chirfort commited on
Commit
5fb8978
·
1 Parent(s): afb6ed3

Refactor popup creation in WeatherMapManager for compact layout and improved readability

Browse files
Files changed (1) hide show
  1. src/geovisor/map_manager.py +109 -282
src/geovisor/map_manager.py CHANGED
@@ -950,142 +950,131 @@ class WeatherMapManager:
950
  return 'green' # Calm winds
951
 
952
  def _create_enhanced_popup(self, city: Dict, mcp_data: Dict = None) -> str:
953
- """Create comprehensive Folium-compatible popup with all weather data"""
954
  try:
955
  city_name = city.get('name', 'Unknown City').title()
956
  forecast = city.get('forecast', [])
957
 
958
  if not forecast:
959
  return f"""
960
- <div style="width: 320px;">
961
- <h3 style="color: #2c3e50; margin-bottom: 10px;">📍 {city_name}</h3>
962
- <p>No weather data available</p>
963
  </div>
964
  """
965
 
966
  current = forecast[0]
967
  next_period = forecast[1] if len(forecast) > 1 else {}
968
 
969
- # Extract comprehensive weather data
970
  temperature = current.get('temperature', 'N/A')
971
  temp_unit = current.get('temperatureUnit', 'F')
972
  feels_like = current.get('apparentTemperature', {})
973
  feels_like_val = feels_like.get('value', 'N/A') if isinstance(feels_like, dict) else 'N/A'
974
  conditions = current.get('shortForecast', 'N/A')
975
- detailed_forecast = current.get('detailedForecast', 'No details available')
976
 
977
- # Wind data
978
  wind_speed = current.get('windSpeed', 'N/A')
979
  wind_direction = current.get('windDirection', '')
980
- wind_gust = current.get('windGust', {})
981
- wind_gust_val = wind_gust.get('value', 'N/A') if isinstance(wind_gust, dict) else 'N/A'
982
-
983
- # Precipitation and humidity
984
  precip_prob = current.get('precipitationProbability', 0) or 0
985
  humidity = current.get('relativeHumidity', {})
986
  humidity_val = humidity.get('value', 'N/A') if isinstance(humidity, dict) else 'N/A'
987
- dewpoint = current.get('dewpoint', {})
988
- dewpoint_val = dewpoint.get('value', 'N/A') if isinstance(dewpoint, dict) else 'N/A'
989
-
990
- # Pressure and visibility
991
- pressure = current.get('barometricPressure', {})
992
- pressure_val = pressure.get('value', 'N/A') if isinstance(pressure, dict) else 'N/A'
993
- visibility = current.get('visibility', {})
994
- visibility_val = visibility.get('value', 'N/A') if isinstance(visibility, dict) else 'N/A'
995
-
996
- # UV and other conditions
997
- heat_index = current.get('heatIndex', {})
998
- heat_index_val = heat_index.get('value', 'N/A') if isinstance(heat_index, dict) else 'N/A'
999
- wind_chill = current.get('windChill', {})
1000
- wind_chill_val = wind_chill.get('value', 'N/A') if isinstance(wind_chill, dict) else 'N/A'
1001
 
1002
  # Get weather emoji
1003
  weather_emoji = self._get_weather_emoji(conditions)
1004
 
1005
- # Create comprehensive popup content
1006
  popup_content = f"""
1007
- <div style="width: 320px; font-family: Arial, sans-serif;">
1008
- <h3 style="color: #2c3e50; margin-bottom: 10px; border-bottom: 2px solid #3498db; padding-bottom: 5px;">
1009
- {weather_emoji} {city_name}
1010
- </h3>
1011
-
1012
- <div style="background: #f8f9fa; padding: 8px; border-radius: 5px; margin-bottom: 8px;">
1013
- <h4 style="color: #e74c3c; margin: 0 0 5px 0; font-size: 14px;">🌡️ Temperature</h4>
1014
- <p style="margin: 2px 0; font-size: 12px;"><b>Current:</b> {temperature{temp_unit}</p>
1015
- <p style="margin: 2px 0; font-size: 12px;"><b>Feels Like:</b> {feels_like_val}°{temp_unit}</p>
1016
- <p style="margin: 2px 0; font-size: 12px;"><b>Dewpoint:</b> {dewpoint_val}°{temp_unit}</p>
1017
- {f'<p style="margin: 2px 0; font-size: 12px;"><b>Heat Index:</b> {heat_index_val}°{temp_unit}</p>' if heat_index_val != 'N/A' else ''}
1018
- {f'<p style="margin: 2px 0; font-size: 12px;"><b>Wind Chill:</b> {wind_chill_val}°{temp_unit}</p>' if wind_chill_val != 'N/A' else ''}
1019
  </div>
1020
 
1021
- <div style="background: #e8f4f8; padding: 8px; border-radius: 5px; margin-bottom: 8px;">
1022
- <h4 style="color: #2980b9; margin: 0 0 5px 0; font-size: 14px;">🌤️ Conditions</h4>
1023
- <p style="margin: 2px 0; font-size: 12px;"><b>Weather:</b> {conditions}</p>
1024
- <p style="margin: 2px 0; font-size: 12px;"><b>Humidity:</b> {humidity_val}%</p>
1025
- <p style="margin: 2px 0; font-size: 12px;"><b>Rain Chance:</b> {precip_prob}%</p>
1026
- <p style="margin: 2px 0; font-size: 12px;"><b>Visibility:</b> {visibility_val} mi</p>
 
 
 
 
 
 
 
 
 
 
 
 
1027
  </div>
1028
 
1029
- <div style="background: #fff3cd; padding: 8px; border-radius: 5px; margin-bottom: 8px;">
1030
- <h4 style="color: #856404; margin: 0 0 5px 0; font-size: 14px;">💨 Wind & Pressure</h4>
1031
- <p style="margin: 2px 0; font-size: 12px;"><b>Wind:</b> {wind_speed} {wind_direction}</p>
1032
- {f'<p style="margin: 2px 0; font-size: 12px;"><b>Gusts:</b> {wind_gust_val} mph</p>' if wind_gust_val != 'N/A' else ''}
1033
- <p style="margin: 2px 0; font-size: 12px;"><b>Pressure:</b> {pressure_val} mb</p>
1034
- </div>
1035
  """
1036
 
1037
- # Add next period forecast
1038
  if next_period:
 
 
 
 
1039
  popup_content += f"""
1040
- <div style="background: #e6f3ff; padding: 8px; border-radius: 5px; margin-bottom: 8px;">
1041
- <h4 style="color: #0056b3; margin: 0 0 5px 0; font-size: 14px;">📅 {next_period.get('name', 'Next Period')}</h4>
1042
- <p style="margin: 2px 0; font-size: 12px;"><b>Temp:</b> {next_period.get('temperature', 'N/A')}°{next_period.get('temperatureUnit', 'F')}</p>
1043
- <p style="margin: 2px 0; font-size: 12px;"><b>Conditions:</b> {next_period.get('shortForecast', 'N/A')}</p>
1044
- <p style="margin: 2px 0; font-size: 12px;"><b>Rain:</b> {next_period.get('precipitationProbability', 0)}%</p>
1045
  </div>
1046
  """
 
 
1047
 
1048
- # Add 5-day outlook if available
1049
  if len(forecast) > 2:
1050
  popup_content += f"""
1051
- <div style="background: #f0f8ff; padding: 8px; border-radius: 5px; margin-bottom: 8px;">
1052
- <h4 style="color: #4169e1; margin: 0 0 5px 0; font-size: 14px;">📊 5-Day Outlook</h4>
 
 
 
1053
  """
1054
- for i, period in enumerate(forecast[2:6]): # Next 4 periods
1055
- period_name = period.get('name', f'Day {i+3}')
1056
  period_temp = period.get('temperature', 'N/A')
1057
- period_conditions = period.get('shortForecast', 'N/A')[:20] + ('...' if len(period.get('shortForecast', '')) > 20 else '')
1058
- popup_content += f'<p style="margin: 1px 0; font-size: 11px;"><b>{period_name}:</b> {period_temp}°F - {period_conditions}</p>'
1059
- popup_content += "</div>"
1060
-
1061
- # Add detailed forecast text
1062
- if detailed_forecast and detailed_forecast != 'No details available':
1063
- truncated_forecast = detailed_forecast[:120] + ('...' if len(detailed_forecast) > 120 else '')
1064
- popup_content += f"""
1065
- <div style="background: #f5f5f5; padding: 8px; border-radius: 5px; margin-bottom: 8px;">
1066
- <h4 style="color: #555; margin: 0 0 5px 0; font-size: 14px;">📝 Detailed Forecast</h4>
1067
- <p style="margin: 0; font-size: 11px; line-height: 1.3;">{truncated_forecast}</p>
1068
- </div>
1069
- """
1070
 
1071
- # Add comprehensive MCP data
1072
  if mcp_data:
1073
- mcp_section = self._create_comprehensive_mcp_section(city_name, mcp_data)
1074
  if mcp_section:
1075
  popup_content += mcp_section
1076
 
1077
- # Add coordinates and footer
1078
  popup_content += f"""
1079
- <div style="background: #e9ecef; padding: 6px; border-radius: 5px; margin-bottom: 5px;">
1080
- <p style="margin: 0; font-size: 11px; text-align: center; color: #6c757d;">
1081
- 📍 Coordinates: {city.get('lat', 'N/A'):.3f}°, {city.get('lon', 'N/A'):.3f}°
1082
- </p>
1083
- </div>
1084
-
1085
- <div style="text-align: center; margin-top: 5px;">
1086
- <p style="font-size: 10px; color: #666; margin: 0;">
1087
  🤖 Enhanced Weather Intelligence
1088
- </p>
1089
  </div>
1090
  </div>
1091
  """
@@ -1102,8 +1091,8 @@ class WeatherMapManager:
1102
  </div>
1103
  """
1104
 
1105
- def _create_comprehensive_mcp_section(self, city_name: str, mcp_data: Dict) -> str:
1106
- """Create comprehensive MCP enhancement section with all available data"""
1107
  if not mcp_data:
1108
  return ""
1109
 
@@ -1118,215 +1107,53 @@ class WeatherMapManager:
1118
  return ""
1119
 
1120
  mcp_html = """
1121
- <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 10px; border-radius: 8px; margin-bottom: 8px;">
1122
- <h4 style="margin: 0 0 8px 0; color: white; font-size: 14px;">🔬 Enhanced Intelligence</h4>
 
 
 
1123
  """
1124
 
 
 
 
1125
  # Air Quality Data
1126
  if 'air_quality' in city_mcp or any('air' in str(k).lower() for k in city_mcp.keys()):
1127
- air_quality_data = city_mcp.get('air_quality', 'Good')
1128
- mcp_html += f"""
1129
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1130
- <strong>🌬️ Air Quality:</strong> {air_quality_data}
1131
- </div>
1132
- """
1133
 
1134
- # Historical Weather Data
1135
  if 'historical_avg' in city_mcp or any('historical' in str(k).lower() for k in city_mcp.keys()):
1136
- historical_data = city_mcp.get('historical_avg', 'No historical data')
1137
- mcp_html += f"""
1138
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1139
- <strong>📊 Historical Average:</strong> {historical_data}
1140
- </div>
1141
- """
1142
-
1143
- # Marine Conditions
1144
- if 'marine_conditions' in city_mcp or any('marine' in str(k).lower() for k in city_mcp.keys()):
1145
- marine_data = city_mcp.get('marine_conditions', 'No marine data')
1146
- mcp_html += f"""
1147
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1148
- <strong>🌊 Marine Conditions:</strong> {marine_data}
1149
- </div>
1150
- """
1151
-
1152
- # Travel Advice
1153
- if 'travel_advice' in city_mcp or any('travel' in str(k).lower() for k in city_mcp.keys()):
1154
- travel_data = city_mcp.get('travel_advice', 'Good travel conditions')
1155
- mcp_html += f"""
1156
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1157
- <strong>✈️ Travel Advice:</strong> {travel_data}
1158
- </div>
1159
- """
1160
-
1161
- # Severe Weather Alerts
1162
- if any('severe' in str(k).lower() or 'alert' in str(k).lower() for k in city_mcp.keys()):
1163
- for key, value in city_mcp.items():
1164
- if 'severe' in key.lower() or 'alert' in key.lower():
1165
- mcp_html += f"""
1166
- <div style="background: rgba(255,100,100,0.3); padding: 6px; margin: 4px 0; border-radius: 4px; border: 1px solid rgba(255,255,255,0.3);">
1167
- <strong>⚠️ Alert:</strong> {str(value)[:60]}{'...' if len(str(value)) > 60 else ''}
1168
- </div>
1169
- """
1170
 
1171
  # UV Index
1172
  if 'uv_index' in city_mcp or any('uv' in str(k).lower() for k in city_mcp.keys()):
1173
- uv_data = city_mcp.get('uv_index', 'Moderate')
1174
- mcp_html += f"""
1175
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1176
- <strong>☀️ UV Index:</strong> {uv_data}
1177
- </div>
1178
- """
1179
-
1180
- # Pollen Count
1181
- if 'pollen' in city_mcp or any('pollen' in str(k).lower() for k in city_mcp.keys()):
1182
- pollen_data = city_mcp.get('pollen', 'Low')
1183
- mcp_html += f"""
1184
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1185
- <strong>🌸 Pollen Count:</strong> {pollen_data}
1186
- </div>
1187
- """
1188
 
1189
- # Agricultural Conditions
1190
- if any('agriculture' in str(k).lower() or 'farming' in str(k).lower() for k in city_mcp.keys()):
1191
- for key, value in city_mcp.items():
1192
- if 'agriculture' in key.lower() or 'farming' in key.lower():
1193
- mcp_html += f"""
1194
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1195
- <strong>🚜 Agriculture:</strong> {str(value)[:50]}{'...' if len(str(value)) > 50 else ''}
1196
- </div>
1197
- """
1198
-
1199
- # Climate Data
1200
- if any('climate' in str(k).lower() for k in city_mcp.keys()):
1201
- for key, value in city_mcp.items():
1202
- if 'climate' in key.lower():
1203
- mcp_html += f"""
1204
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1205
- <strong>🌍 Climate:</strong> {str(value)[:50]}{'...' if len(str(value)) > 50 else ''}
1206
- </div>
1207
- """
1208
-
1209
- # Ocean/Water Data
1210
- if any('ocean' in str(k).lower() or 'water' in str(k).lower() or 'tide' in str(k).lower() for k in city_mcp.keys()):
1211
- for key, value in city_mcp.items():
1212
- if any(term in key.lower() for term in ['ocean', 'water', 'tide', 'wave']):
1213
- mcp_html += f"""
1214
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1215
- <strong>🌊 Ocean Data:</strong> {str(value)[:50]}{'...' if len(str(value)) > 50 else ''}
1216
- </div>
1217
- """
1218
-
1219
- # Satellite Data
1220
- if any('satellite' in str(k).lower() for k in city_mcp.keys()):
1221
- for key, value in city_mcp.items():
1222
- if 'satellite' in key.lower():
1223
- mcp_html += f"""
1224
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1225
- <strong>🛰️ Satellite:</strong> {str(value)[:50]}{'...' if len(str(value)) > 50 else ''}
1226
- </div>
1227
- """
1228
-
1229
- # Storm Data
1230
- if any('storm' in str(k).lower() or 'hurricane' in str(k).lower() or 'tornado' in str(k).lower() for k in city_mcp.keys()):
1231
- for key, value in city_mcp.items():
1232
- if any(term in key.lower() for term in ['storm', 'hurricane', 'tornado', 'cyclone']):
1233
- mcp_html += f"""
1234
- <div style="background: rgba(255,100,100,0.3); padding: 6px; margin: 4px 0; border-radius: 4px; border: 1px solid rgba(255,255,255,0.3);">
1235
- <strong>🌪️ Storm Data:</strong> {str(value)[:50]}{'...' if len(str(value)) > 50 else ''}
1236
- </div>
1237
- """
1238
-
1239
- # Any other MCP tool results (catch-all)
1240
- excluded_keys = ['air_quality', 'historical_avg', 'marine_conditions', 'travel_advice', 'uv_index', 'pollen']
1241
- for tool_name, tool_result in city_mcp.items():
1242
- if tool_name not in excluded_keys and not any(term in tool_name.lower() for term in ['severe', 'alert', 'agriculture', 'farming', 'climate', 'ocean', 'water', 'tide', 'wave', 'satellite', 'storm', 'hurricane', 'tornado', 'cyclone']):
1243
- # Format tool name for display
1244
- display_name = tool_name.replace('_', ' ').title()
1245
- result_str = str(tool_result)[:50] + ('...' if len(str(tool_result)) > 50 else '')
1246
-
1247
- # Choose appropriate emoji based on tool name
1248
- emoji = "🤖"
1249
- if 'temperature' in tool_name.lower():
1250
- emoji = "🌡️"
1251
- elif 'wind' in tool_name.lower():
1252
- emoji = "💨"
1253
- elif 'pressure' in tool_name.lower():
1254
- emoji = "⚡"
1255
- elif 'humidity' in tool_name.lower():
1256
- emoji = "💧"
1257
- elif 'forecast' in tool_name.lower():
1258
- emoji = "📅"
1259
- elif 'radar' in tool_name.lower():
1260
- emoji = "📡"
1261
-
1262
- mcp_html += f"""
1263
- <div style="background: rgba(255,255,255,0.2); padding: 6px; margin: 4px 0; border-radius: 4px;">
1264
- <strong>{emoji} {display_name}:</strong> {result_str}
1265
- </div>
1266
- """
1267
-
1268
- mcp_html += "</div>"
1269
- return mcp_html
1270
-
1271
- def _create_simple_mcp_section(self, city_name: str, mcp_data: Dict) -> str:
1272
- """Create simplified MCP section for popup"""
1273
- if not mcp_data:
1274
- return ""
1275
-
1276
- # Find matching city data (case insensitive)
1277
- city_mcp = None
1278
- for key, value in mcp_data.items():
1279
- if key.lower() == city_name.lower():
1280
- city_mcp = value
1281
- break
1282
-
1283
- if not city_mcp:
1284
- return ""
1285
-
1286
- mcp_html = '<h4 style="color: #4a90e2; margin: 10px 0 5px 0;">Enhanced Data</h4>'
1287
-
1288
- # Add air quality data
1289
- if 'air_quality' in city_mcp:
1290
- mcp_html += f'<p style="margin: 3px 0;"><b>Air Quality:</b> {city_mcp["air_quality"]}</p>'
1291
-
1292
- # Add historical data
1293
- if 'historical_avg' in city_mcp:
1294
- mcp_html += f'<p style="margin: 3px 0;"><b>Historical Avg:</b> {city_mcp["historical_avg"]}°F</p>'
1295
-
1296
- # Add marine conditions
1297
  if 'marine_conditions' in city_mcp:
1298
- mcp_html += f'<p style="margin: 3px 0;"><b>Marine:</b> {city_mcp["marine_conditions"]}</p>'
 
 
 
 
1299
 
1300
- # Add travel advice
1301
- if 'travel_advice' in city_mcp:
1302
- mcp_html += f'<p style="margin: 3px 0;"><b>Travel:</b> {city_mcp["travel_advice"]}</p>'
1303
 
1304
- return mcp_html
 
 
1305
 
1306
- # Add any MCP tool results
1307
- for tool_name, tool_result in city_mcp.items():
1308
- if tool_name not in ['air_quality', 'historical_avg', 'marine_conditions', 'travel_advice']:
1309
- if tool_name == 'get_air_quality_data':
1310
- enhancements_html += f"""
1311
- <div style="background: rgba(255,255,255,0.4); padding: 8px; margin: 5px 0; border-radius: 6px;">
1312
- <strong>🌬️ Air Quality Analysis:</strong> {str(tool_result)[:100]}...
1313
- </div>
1314
- """
1315
- elif tool_name == 'get_historical_weather':
1316
- enhancements_html += f"""
1317
- <div style="background: rgba(255,255,255,0.4); padding: 8px; margin: 5px 0; border-radius: 6px;">
1318
- <strong>📊 Historical Analysis:</strong> {str(tool_result)[:100]}...
1319
- </div>
1320
- """
1321
- elif tool_name == 'get_severe_weather_outlook':
1322
- enhancements_html += f"""
1323
- <div style="background: rgba(255,255,255,0.4); padding: 8px; margin: 5px 0; border-radius: 6px;">
1324
- <strong>⚠️ Severe Weather:</strong> {str(tool_result)[:100]}...
1325
- </div>
1326
- """
1327
 
1328
- enhancements_html += "</div>"
1329
- return enhancements_html
1330
 
1331
 
1332
  def create_map_manager() -> WeatherMapManager:
 
950
  return 'green' # Calm winds
951
 
952
  def _create_enhanced_popup(self, city: Dict, mcp_data: Dict = None) -> str:
953
+ """Create compact horizontal-layout popup for better map integration"""
954
  try:
955
  city_name = city.get('name', 'Unknown City').title()
956
  forecast = city.get('forecast', [])
957
 
958
  if not forecast:
959
  return f"""
960
+ <div style="width: 320px; font-family: Arial, sans-serif;">
961
+ <h3 style="color: #2c3e50; margin-bottom: 10px; text-align: center;">📍 {city_name}</h3>
962
+ <p style="text-align: center; color: #777;">No weather data available</p>
963
  </div>
964
  """
965
 
966
  current = forecast[0]
967
  next_period = forecast[1] if len(forecast) > 1 else {}
968
 
969
+ # Extract essential weather data
970
  temperature = current.get('temperature', 'N/A')
971
  temp_unit = current.get('temperatureUnit', 'F')
972
  feels_like = current.get('apparentTemperature', {})
973
  feels_like_val = feels_like.get('value', 'N/A') if isinstance(feels_like, dict) else 'N/A'
974
  conditions = current.get('shortForecast', 'N/A')
 
975
 
976
+ # Compact weather details
977
  wind_speed = current.get('windSpeed', 'N/A')
978
  wind_direction = current.get('windDirection', '')
 
 
 
 
979
  precip_prob = current.get('precipitationProbability', 0) or 0
980
  humidity = current.get('relativeHumidity', {})
981
  humidity_val = humidity.get('value', 'N/A') if isinstance(humidity, dict) else 'N/A'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982
 
983
  # Get weather emoji
984
  weather_emoji = self._get_weather_emoji(conditions)
985
 
986
+ # Create compact horizontal popup content
987
  popup_content = f"""
988
+ <div style="width: 320px; font-family: Arial, sans-serif; line-height: 1.2;">
989
+ <!-- Header with gradient background -->
990
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 8px; border-radius: 8px 8px 0 0; text-align: center; margin-bottom: 8px;">
991
+ <h3 style="margin: 0; font-size: 16px; font-weight: bold;">
992
+ {weather_emoji} {city_name}
993
+ </h3>
994
+ <p style="margin: 2px 0 0 0; font-size: 11px; opacity: 0.9;">
995
+ 📍 {city.get('lat', 'N/A'):.3f}°, {city.get('lon', 'N/A'):.3f
996
+ </p>
 
 
 
997
  </div>
998
 
999
+ <!-- Main temperature and conditions in horizontal layout -->
1000
+ <div style="display: flex; gap: 8px; margin-bottom: 8px;">
1001
+ <div style="flex: 1; background: #f8f9fa; padding: 8px; border-radius: 6px; border-left: 4px solid #e74c3c;">
1002
+ <div style="font-size: 20px; font-weight: bold; color: #e74c3c; margin-bottom: 2px;">
1003
+ {temperature}°{temp_unit}
1004
+ </div>
1005
+ <div style="font-size: 10px; color: #666;">
1006
+ Feels {feels_like_val}°{temp_unit}
1007
+ </div>
1008
+ </div>
1009
+ <div style="flex: 2; background: #e8f4f8; padding: 8px; border-radius: 6px; border-left: 4px solid #2980b9;">
1010
+ <div style="font-size: 12px; font-weight: bold; color: #2980b9; margin-bottom: 2px;">
1011
+ {conditions}
1012
+ </div>
1013
+ <div style="font-size: 10px; color: #666;">
1014
+ 💧 {humidity_val}% • 🌧️ {precip_prob}%
1015
+ </div>
1016
+ </div>
1017
  </div>
1018
 
1019
+ <!-- Wind and additional data in compact grid -->
1020
+ <div style="display: flex; gap: 6px; margin-bottom: 8px;">
1021
+ <div style="flex: 1; background: #fff3cd; padding: 6px; border-radius: 4px; text-align: center;">
1022
+ <div style="font-size: 11px; font-weight: bold; color: #856404;">💨 Wind</div>
1023
+ <div style="font-size: 10px; color: #666;">{wind_speed} {wind_direction}</div>
1024
+ </div>
1025
  """
1026
 
1027
+ # Add next period in compact format
1028
  if next_period:
1029
+ next_temp = next_period.get('temperature', 'N/A')
1030
+ next_conditions = next_period.get('shortForecast', 'N/A')[:15] + ('...' if len(next_period.get('shortForecast', '')) > 15 else '')
1031
+ next_name = next_period.get('name', 'Next')[:8] # Truncate long period names
1032
+
1033
  popup_content += f"""
1034
+ <div style="flex: 2; background: #e6f3ff; padding: 6px; border-radius: 4px; text-align: center;">
1035
+ <div style="font-size: 11px; font-weight: bold; color: #0056b3;">📅 {next_name}</div>
1036
+ <div style="font-size: 10px; color: #666;">{next_tempF • {next_conditions}</div>
1037
+ </div>
 
1038
  </div>
1039
  """
1040
+ else:
1041
+ popup_content += "</div>"
1042
 
1043
+ # Add compact 3-day outlook if available
1044
  if len(forecast) > 2:
1045
  popup_content += f"""
1046
+ <div style="background: #f0f8ff; padding: 6px; border-radius: 6px; margin-bottom: 8px;">
1047
+ <div style="font-size: 11px; font-weight: bold; color: #4169e1; margin-bottom: 4px; text-align: center;">
1048
+ 📊 3-Day Outlook
1049
+ </div>
1050
+ <div style="display: flex; gap: 4px;">
1051
  """
1052
+ for i, period in enumerate(forecast[2:5]): # Next 3 periods only
1053
+ period_name = period.get('name', f'D{i+3}')[:3] # Very short names
1054
  period_temp = period.get('temperature', 'N/A')
1055
+ period_emoji = self._get_weather_emoji(period.get('shortForecast', ''))
1056
+
1057
+ popup_content += f"""
1058
+ <div style="flex: 1; text-align: center; background: rgba(255,255,255,0.7); padding: 4px; border-radius: 3px;">
1059
+ <div style="font-size: 9px; font-weight: bold;">{period_name}</div>
1060
+ <div style="font-size: 12px;">{period_emoji}</div>
1061
+ <div style="font-size: 9px;">{period_temp}°F</div>
1062
+ </div>
1063
+ """
1064
+ popup_content += "</div></div>"
 
 
 
1065
 
1066
+ # Add compact MCP data if available
1067
  if mcp_data:
1068
+ mcp_section = self._create_compact_mcp_section(city_name, mcp_data)
1069
  if mcp_section:
1070
  popup_content += mcp_section
1071
 
1072
+ # Compact footer
1073
  popup_content += f"""
1074
+ <div style="background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px; border-radius: 0 0 8px 8px; text-align: center;">
1075
+ <div style="font-size: 9px; opacity: 0.9;">
 
 
 
 
 
 
1076
  🤖 Enhanced Weather Intelligence
1077
+ </div>
1078
  </div>
1079
  </div>
1080
  """
 
1091
  </div>
1092
  """
1093
 
1094
+ def _create_compact_mcp_section(self, city_name: str, mcp_data: Dict) -> str:
1095
+ """Create compact MCP section for horizontal layout popup"""
1096
  if not mcp_data:
1097
  return ""
1098
 
 
1107
  return ""
1108
 
1109
  mcp_html = """
1110
+ <div style="background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); color: white; padding: 6px; border-radius: 6px; margin-bottom: 6px;">
1111
+ <div style="font-size: 11px; font-weight: bold; margin-bottom: 4px; text-align: center;">
1112
+ 🔬 Enhanced Intelligence
1113
+ </div>
1114
+ <div style="display: flex; flex-wrap: wrap; gap: 4px;">
1115
  """
1116
 
1117
+ # Compact data items in horizontal pills
1118
+ compact_items = []
1119
+
1120
  # Air Quality Data
1121
  if 'air_quality' in city_mcp or any('air' in str(k).lower() for k in city_mcp.keys()):
1122
+ air_quality = str(city_mcp.get('air_quality', 'Good'))[:10]
1123
+ compact_items.append(f'<span style="background: rgba(255,255,255,0.3); padding: 2px 6px; border-radius: 10px; font-size: 9px; white-space: nowrap;">🌬️ {air_quality}</span>')
 
 
 
 
1124
 
1125
+ # Historical Data
1126
  if 'historical_avg' in city_mcp or any('historical' in str(k).lower() for k in city_mcp.keys()):
1127
+ historical = str(city_mcp.get('historical_avg', 'N/A'))[:8]
1128
+ compact_items.append(f'<span style="background: rgba(255,255,255,0.3); padding: 2px 6px; border-radius: 10px; font-size: 9px; white-space: nowrap;">📊 {historical}</span>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
 
1130
  # UV Index
1131
  if 'uv_index' in city_mcp or any('uv' in str(k).lower() for k in city_mcp.keys()):
1132
+ uv = str(city_mcp.get('uv_index', 'Mod'))[:8]
1133
+ compact_items.append(f'<span style="background: rgba(255,255,255,0.3); padding: 2px 6px; border-radius: 10px; font-size: 9px; white-space: nowrap;">☀️ {uv}</span>')
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
 
1135
+ # Marine/Travel (prioritize most relevant)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
  if 'marine_conditions' in city_mcp:
1137
+ marine = str(city_mcp.get('marine_conditions', ''))[:8]
1138
+ compact_items.append(f'<span style="background: rgba(255,255,255,0.3); padding: 2px 6px; border-radius: 10px; font-size: 9px; white-space: nowrap;">🌊 {marine}</span>')
1139
+ elif 'travel_advice' in city_mcp:
1140
+ travel = str(city_mcp.get('travel_advice', ''))[:8]
1141
+ compact_items.append(f'<span style="background: rgba(255,255,255,0.3); padding: 2px 6px; border-radius: 10px; font-size: 9px; white-space: nowrap;">✈️ {travel}</span>')
1142
 
1143
+ # Severe Weather Alerts (high priority)
1144
+ if any('severe' in str(k).lower() or 'alert' in str(k).lower() for k in city_mcp.keys()):
1145
+ compact_items.append(f'<span style="background: rgba(255,100,100,0.6); padding: 2px 6px; border-radius: 10px; font-size: 9px; white-space: nowrap;">⚠️ Alert</span>')
1146
 
1147
+ # Add up to 4 most important items
1148
+ for item in compact_items[:4]:
1149
+ mcp_html += item
1150
 
1151
+ mcp_html += """
1152
+ </div>
1153
+ </div>
1154
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1155
 
1156
+ return mcp_html
 
1157
 
1158
 
1159
  def create_map_manager() -> WeatherMapManager: