euler314 commited on
Commit
4a85449
·
verified ·
1 Parent(s): e55c416

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +780 -872
app.py CHANGED
@@ -801,6 +801,753 @@ def categorize_typhoon_by_standard(wind_speed, standard='atlantic'):
801
  return 'Tropical Storm', '#0000FF' # Blue
802
  return 'Tropical Depression', '#808080' # Gray
803
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
  # -----------------------------
805
  # FIXED: ADVANCED ML FEATURES WITH ROBUST ERROR HANDLING
806
  # -----------------------------
@@ -1508,712 +2255,6 @@ def create_separate_clustering_plots(storm_features, typhoon_data, method='umap'
1508
  )
1509
  return error_fig, error_fig, error_fig, error_fig, f"Error in clustering: {str(e)}"
1510
 
1511
- # -----------------------------
1512
- # ENHANCED: Advanced Prediction System with Route Forecasting
1513
- # -----------------------------
1514
-
1515
- def create_advanced_prediction_model(typhoon_data):
1516
- """Create advanced ML model for intensity and route prediction"""
1517
- try:
1518
- if typhoon_data is None or typhoon_data.empty:
1519
- return None, "No data available for model training"
1520
-
1521
- # Prepare training data
1522
- features = []
1523
- targets = []
1524
-
1525
- for sid in typhoon_data['SID'].unique():
1526
- storm_data = typhoon_data[typhoon_data['SID'] == sid].sort_values('ISO_TIME')
1527
-
1528
- if len(storm_data) < 3: # Need at least 3 points for prediction
1529
- continue
1530
-
1531
- for i in range(len(storm_data) - 1):
1532
- current = storm_data.iloc[i]
1533
- next_point = storm_data.iloc[i + 1]
1534
-
1535
- # Extract features (current state)
1536
- feature_row = []
1537
-
1538
- # Current position
1539
- feature_row.extend([
1540
- current.get('LAT', 20),
1541
- current.get('LON', 140)
1542
- ])
1543
-
1544
- # Current intensity
1545
- feature_row.extend([
1546
- current.get('USA_WIND', 30),
1547
- current.get('USA_PRES', 1000)
1548
- ])
1549
-
1550
- # Time features
1551
- if 'ISO_TIME' in current and pd.notna(current['ISO_TIME']):
1552
- month = current['ISO_TIME'].month
1553
- day_of_year = current['ISO_TIME'].dayofyear
1554
- else:
1555
- month = 9 # Peak season default
1556
- day_of_year = 250
1557
-
1558
- feature_row.extend([month, day_of_year])
1559
-
1560
- # Motion features (if previous point exists)
1561
- if i > 0:
1562
- prev = storm_data.iloc[i - 1]
1563
- dlat = current.get('LAT', 20) - prev.get('LAT', 20)
1564
- dlon = current.get('LON', 140) - prev.get('LON', 140)
1565
- speed = np.sqrt(dlat**2 + dlon**2)
1566
- bearing = np.arctan2(dlat, dlon)
1567
- else:
1568
- speed = 0
1569
- bearing = 0
1570
-
1571
- feature_row.extend([speed, bearing])
1572
-
1573
- features.append(feature_row)
1574
-
1575
- # Target: next position and intensity
1576
- targets.append([
1577
- next_point.get('LAT', 20),
1578
- next_point.get('LON', 140),
1579
- next_point.get('USA_WIND', 30)
1580
- ])
1581
-
1582
- if len(features) < 10: # Need sufficient training data
1583
- return None, "Insufficient data for model training"
1584
-
1585
- # Train model
1586
- X = np.array(features)
1587
- y = np.array(targets)
1588
-
1589
- # Split data
1590
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
1591
-
1592
- # Create separate models for position and intensity
1593
- models = {}
1594
-
1595
- # Position model (lat, lon)
1596
- pos_model = RandomForestRegressor(n_estimators=100, random_state=42)
1597
- pos_model.fit(X_train, y_train[:, :2])
1598
- models['position'] = pos_model
1599
-
1600
- # Intensity model (wind speed)
1601
- int_model = RandomForestRegressor(n_estimators=100, random_state=42)
1602
- int_model.fit(X_train, y_train[:, 2])
1603
- models['intensity'] = int_model
1604
-
1605
- # Calculate model performance
1606
- pos_pred = pos_model.predict(X_test)
1607
- int_pred = int_model.predict(X_test)
1608
-
1609
- pos_mae = mean_absolute_error(y_test[:, :2], pos_pred)
1610
- int_mae = mean_absolute_error(y_test[:, 2], int_pred)
1611
-
1612
- model_info = f"Position MAE: {pos_mae:.2f}°, Intensity MAE: {int_mae:.2f} kt"
1613
-
1614
- return models, model_info
1615
-
1616
- except Exception as e:
1617
- return None, f"Error creating prediction model: {str(e)}"
1618
-
1619
- def get_realistic_genesis_locations():
1620
- """Get realistic typhoon genesis regions based on climatology"""
1621
- return {
1622
- "Western Pacific Main Development Region": {"lat": 12.5, "lon": 145.0, "description": "Peak activity zone (Guam area)"},
1623
- "South China Sea": {"lat": 15.0, "lon": 115.0, "description": "Secondary development region"},
1624
- "Philippine Sea": {"lat": 18.0, "lon": 135.0, "description": "Recurving storm region"},
1625
- "Marshall Islands": {"lat": 8.0, "lon": 165.0, "description": "Eastern development zone"},
1626
- "Monsoon Trough": {"lat": 10.0, "lon": 130.0, "description": "Monsoon-driven genesis"},
1627
- "ITCZ Region": {"lat": 6.0, "lon": 140.0, "description": "Near-equatorial development"},
1628
- "Subtropical Region": {"lat": 22.0, "lon": 125.0, "description": "Late season development"},
1629
- "Bay of Bengal": {"lat": 15.0, "lon": 88.0, "description": "Indian Ocean cyclones"},
1630
- "Eastern Pacific": {"lat": 12.0, "lon": -105.0, "description": "Hurricane development zone"},
1631
- "Atlantic MDR": {"lat": 12.0, "lon": -45.0, "description": "Main Development Region"}
1632
- }
1633
-
1634
- def predict_storm_route_and_intensity_realistic(genesis_region, month, oni_value, models=None, forecast_hours=72, use_advanced_physics=True):
1635
- """Realistic prediction with proper typhoon speeds and development"""
1636
- try:
1637
- genesis_locations = get_realistic_genesis_locations()
1638
-
1639
- if genesis_region not in genesis_locations:
1640
- genesis_region = "Western Pacific Main Development Region" # Default
1641
-
1642
- genesis_info = genesis_locations[genesis_region]
1643
- lat = genesis_info["lat"]
1644
- lon = genesis_info["lon"]
1645
-
1646
- results = {
1647
- 'current_prediction': {},
1648
- 'route_forecast': [],
1649
- 'confidence_scores': {},
1650
- 'model_info': 'Realistic Genesis Model',
1651
- 'genesis_info': genesis_info
1652
- }
1653
-
1654
- # REALISTIC starting intensity - Tropical Depression level
1655
- base_intensity = 30 # Start at TD level (25-35 kt)
1656
-
1657
- # Environmental factors for genesis
1658
- if oni_value > 1.0: # Strong El Niño - suppressed development
1659
- intensity_modifier = -6
1660
- elif oni_value > 0.5: # Moderate El Niño
1661
- intensity_modifier = -3
1662
- elif oni_value < -1.0: # Strong La Niña - enhanced development
1663
- intensity_modifier = +8
1664
- elif oni_value < -0.5: # Moderate La Niña
1665
- intensity_modifier = +5
1666
- else: # Neutral
1667
- intensity_modifier = oni_value * 2
1668
-
1669
- # Seasonal genesis effects
1670
- seasonal_factors = {
1671
- 1: -8, 2: -6, 3: -4, 4: -2, 5: 2, 6: 6,
1672
- 7: 10, 8: 12, 9: 15, 10: 10, 11: 4, 12: -5
1673
- }
1674
- seasonal_modifier = seasonal_factors.get(month, 0)
1675
-
1676
- # Genesis region favorability
1677
- region_factors = {
1678
- "Western Pacific Main Development Region": 8,
1679
- "South China Sea": 4,
1680
- "Philippine Sea": 5,
1681
- "Marshall Islands": 7,
1682
- "Monsoon Trough": 6,
1683
- "ITCZ Region": 3,
1684
- "Subtropical Region": 2,
1685
- "Bay of Bengal": 4,
1686
- "Eastern Pacific": 6,
1687
- "Atlantic MDR": 5
1688
- }
1689
- region_modifier = region_factors.get(genesis_region, 0)
1690
-
1691
- # Calculate realistic starting intensity (TD level)
1692
- predicted_intensity = base_intensity + intensity_modifier + seasonal_modifier + region_modifier
1693
- predicted_intensity = max(25, min(40, predicted_intensity)) # Keep in TD-weak TS range
1694
-
1695
- # Add realistic uncertainty for genesis
1696
- intensity_uncertainty = np.random.normal(0, 2)
1697
- predicted_intensity += intensity_uncertainty
1698
- predicted_intensity = max(25, min(38, predicted_intensity)) # TD range
1699
-
1700
- results['current_prediction'] = {
1701
- 'intensity_kt': predicted_intensity,
1702
- 'pressure_hpa': 1008 - (predicted_intensity - 25) * 0.6, # Realistic TD pressure
1703
- 'category': categorize_typhoon_enhanced(predicted_intensity),
1704
- 'genesis_region': genesis_region
1705
- }
1706
-
1707
- # REALISTIC route prediction with proper typhoon speeds
1708
- current_lat = lat
1709
- current_lon = lon
1710
- current_intensity = predicted_intensity
1711
-
1712
- route_points = []
1713
-
1714
- # Track storm development over time with REALISTIC SPEEDS
1715
- for hour in range(0, forecast_hours + 6, 6):
1716
-
1717
- # REALISTIC typhoon motion - much faster speeds
1718
- # Typical typhoon forward speed: 15-25 km/h (0.14-0.23°/hour)
1719
-
1720
- # Base forward speed depends on latitude and storm intensity
1721
- if current_lat < 20: # Low latitude - slower
1722
- base_speed = 0.12 # ~13 km/h
1723
- elif current_lat < 30: # Mid latitude - moderate
1724
- base_speed = 0.18 # ~20 km/h
1725
- else: # High latitude - faster
1726
- base_speed = 0.25 # ~28 km/h
1727
-
1728
- # Intensity affects speed (stronger storms can move faster)
1729
- intensity_speed_factor = 1.0 + (current_intensity - 50) / 200
1730
- base_speed *= max(0.8, min(1.4, intensity_speed_factor))
1731
-
1732
- # Beta drift (Coriolis effect) - realistic values
1733
- beta_drift_lat = 0.02 * np.sin(np.radians(current_lat))
1734
- beta_drift_lon = -0.05 * np.cos(np.radians(current_lat))
1735
-
1736
- # Seasonal steering patterns with realistic speeds
1737
- if month in [6, 7, 8, 9]: # Peak season
1738
- ridge_strength = 1.2
1739
- ridge_position = 32 + 4 * np.sin(2 * np.pi * (month - 6) / 4)
1740
- else: # Off season
1741
- ridge_strength = 0.9
1742
- ridge_position = 28
1743
-
1744
- # REALISTIC motion based on position relative to subtropical ridge
1745
- if current_lat < ridge_position - 10: # Well south of ridge - westward movement
1746
- lat_tendency = base_speed * 0.3 + beta_drift_lat # Slight poleward
1747
- lon_tendency = -base_speed * 0.9 + beta_drift_lon # Strong westward
1748
- elif current_lat > ridge_position - 3: # Near ridge - recurvature
1749
- lat_tendency = base_speed * 0.8 + beta_drift_lat # Strong poleward
1750
- lon_tendency = base_speed * 0.4 + beta_drift_lon # Eastward
1751
- else: # In between - normal WNW motion
1752
- lat_tendency = base_speed * 0.4 + beta_drift_lat # Moderate poleward
1753
- lon_tendency = -base_speed * 0.7 + beta_drift_lon # Moderate westward
1754
-
1755
- # ENSO steering modulation (realistic effects)
1756
- if oni_value > 0.5: # El Niño - more eastward/poleward motion
1757
- lon_tendency += 0.05
1758
- lat_tendency += 0.02
1759
- elif oni_value < -0.5: # La Niña - more westward motion
1760
- lon_tendency -= 0.08
1761
- lat_tendency -= 0.01
1762
-
1763
- # Add motion uncertainty that grows with time (realistic error growth)
1764
- motion_uncertainty = 0.02 + (hour / 120) * 0.04
1765
- lat_noise = np.random.normal(0, motion_uncertainty)
1766
- lon_noise = np.random.normal(0, motion_uncertainty)
1767
-
1768
- # Update position with realistic speeds
1769
- current_lat += lat_tendency + lat_noise
1770
- current_lon += lon_tendency + lon_noise
1771
-
1772
- # REALISTIC intensity evolution with proper development cycles
1773
-
1774
- # Development phase (first 48-72 hours) - realistic intensification
1775
- if hour <= 48:
1776
- if current_intensity < 50: # Still weak - rapid development possible
1777
- if 10 <= current_lat <= 25 and 115 <= current_lon <= 165: # Favorable environment
1778
- intensity_tendency = 4.5 if current_intensity < 35 else 3.0
1779
- elif 120 <= current_lon <= 155 and 15 <= current_lat <= 20: # Best environment
1780
- intensity_tendency = 6.0 if current_intensity < 40 else 4.0
1781
- else:
1782
- intensity_tendency = 2.0
1783
- elif current_intensity < 80: # Moderate intensity
1784
- intensity_tendency = 2.5 if (120 <= current_lon <= 155 and 10 <= current_lat <= 25) else 1.0
1785
- else: # Already strong
1786
- intensity_tendency = 1.0
1787
-
1788
- # Mature phase (48-120 hours) - peak intensity maintenance
1789
- elif hour <= 120:
1790
- if current_lat < 25 and current_lon > 120: # Still in favorable waters
1791
- if current_intensity < 120:
1792
- intensity_tendency = 1.5
1793
- else:
1794
- intensity_tendency = 0.0 # Maintain intensity
1795
- else:
1796
- intensity_tendency = -1.5
1797
-
1798
- # Extended phase (120+ hours) - gradual weakening
1799
- else:
1800
- if current_lat < 30 and current_lon > 115:
1801
- intensity_tendency = -2.0 # Slow weakening
1802
- else:
1803
- intensity_tendency = -3.5 # Faster weakening
1804
-
1805
- # Environmental modulation (realistic effects)
1806
- if current_lat > 35: # High latitude - rapid weakening
1807
- intensity_tendency -= 12
1808
- elif current_lat > 30: # Moderate latitude
1809
- intensity_tendency -= 5
1810
- elif current_lon < 110: # Land interaction
1811
- intensity_tendency -= 15
1812
- elif 125 <= current_lon <= 155 and 10 <= current_lat <= 25: # Warm pool
1813
- intensity_tendency += 2
1814
- elif 160 <= current_lon <= 180 and 15 <= current_lat <= 30: # Still warm
1815
- intensity_tendency += 1
1816
-
1817
- # SST effects (realistic temperature impact)
1818
- if current_lat < 8: # Very warm but weak Coriolis
1819
- intensity_tendency += 0.5
1820
- elif 8 <= current_lat <= 20: # Sweet spot for development
1821
- intensity_tendency += 2.0
1822
- elif 20 < current_lat <= 30: # Marginal
1823
- intensity_tendency -= 1.0
1824
- elif current_lat > 30: # Cool waters
1825
- intensity_tendency -= 4.0
1826
-
1827
- # Shear effects (simplified but realistic)
1828
- if month in [12, 1, 2, 3]: # High shear season
1829
- intensity_tendency -= 2.0
1830
- elif month in [7, 8, 9]: # Low shear season
1831
- intensity_tendency += 1.0
1832
-
1833
- # Update intensity with realistic bounds and variability
1834
- intensity_noise = np.random.normal(0, 1.5) # Small random fluctuations
1835
- current_intensity += intensity_tendency + intensity_noise
1836
- current_intensity = max(20, min(185, current_intensity)) # Realistic range
1837
-
1838
- # Calculate confidence based on forecast time and environment
1839
- base_confidence = 0.92
1840
- time_penalty = (hour / 120) * 0.45
1841
- environment_penalty = 0.15 if current_lat > 30 or current_lon < 115 else 0
1842
- confidence = max(0.25, base_confidence - time_penalty - environment_penalty)
1843
-
1844
- # Determine development stage
1845
- if hour <= 24:
1846
- stage = 'Genesis'
1847
- elif hour <= 72:
1848
- stage = 'Development'
1849
- elif hour <= 120:
1850
- stage = 'Mature'
1851
- elif hour <= 240:
1852
- stage = 'Extended'
1853
- else:
1854
- stage = 'Long-term'
1855
-
1856
- route_points.append({
1857
- 'hour': hour,
1858
- 'lat': current_lat,
1859
- 'lon': current_lon,
1860
- 'intensity_kt': current_intensity,
1861
- 'category': categorize_typhoon_enhanced(current_intensity),
1862
- 'confidence': confidence,
1863
- 'development_stage': stage,
1864
- 'forward_speed_kmh': base_speed * 111, # Convert to km/h
1865
- 'pressure_hpa': max(900, 1013 - (current_intensity - 25) * 0.9)
1866
- })
1867
-
1868
- results['route_forecast'] = route_points
1869
-
1870
- # Realistic confidence scores
1871
- results['confidence_scores'] = {
1872
- 'genesis': 0.88,
1873
- 'early_development': 0.82,
1874
- 'position_24h': 0.85,
1875
- 'position_48h': 0.78,
1876
- 'position_72h': 0.68,
1877
- 'intensity_24h': 0.75,
1878
- 'intensity_48h': 0.65,
1879
- 'intensity_72h': 0.55,
1880
- 'long_term': max(0.3, 0.8 - (forecast_hours / 240) * 0.5)
1881
- }
1882
-
1883
- # Model information
1884
- results['model_info'] = f"Enhanced Realistic Model - {genesis_region}"
1885
-
1886
- return results
1887
-
1888
- except Exception as e:
1889
- logging.error(f"Realistic prediction error: {str(e)}")
1890
- return {
1891
- 'error': f"Prediction error: {str(e)}",
1892
- 'current_prediction': {'intensity_kt': 30, 'category': 'Tropical Depression'},
1893
- 'route_forecast': [],
1894
- 'confidence_scores': {},
1895
- 'model_info': 'Error in prediction'
1896
- }
1897
-
1898
- # -----------------------------
1899
- # NEW: SIMPLIFIED MULTI-ROUTE PREDICTION FUNCTIONS
1900
- # -----------------------------
1901
-
1902
- def generate_multiple_route_predictions(month, oni_value, num_routes=5):
1903
- """Generate multiple realistic route predictions for animation"""
1904
-
1905
- # Realistic genesis regions for variety
1906
- genesis_regions = [
1907
- "Western Pacific Main Development Region",
1908
- "South China Sea",
1909
- "Philippine Sea",
1910
- "Marshall Islands",
1911
- "Monsoon Trough"
1912
- ]
1913
-
1914
- all_predictions = []
1915
-
1916
- for i in range(num_routes):
1917
- # Use different genesis regions and slight ONI variations for diversity
1918
- region = genesis_regions[i % len(genesis_regions)]
1919
-
1920
- # Add small random variation to ONI for route diversity
1921
- oni_variation = oni_value + np.random.normal(0, 0.2)
1922
-
1923
- # Generate prediction for this route
1924
- prediction = predict_storm_route_and_intensity_realistic(
1925
- region,
1926
- month,
1927
- oni_variation,
1928
- forecast_hours=120, # 5 day forecast
1929
- use_advanced_physics=True
1930
- )
1931
-
1932
- prediction['route_id'] = i + 1
1933
- prediction['genesis_region'] = region
1934
- prediction['oni_used'] = oni_variation
1935
- all_predictions.append(prediction)
1936
-
1937
- return all_predictions
1938
-
1939
- def create_multi_route_animation(all_predictions, enable_animation=True):
1940
- """Create animated visualization showing multiple predicted routes"""
1941
-
1942
- # Route colors for multiple predictions
1943
- route_colors = ['#FF0066', '#00FF66', '#6600FF', '#FF6600', '#0066FF']
1944
-
1945
- if not all_predictions or not any(pred.get('route_forecast') for pred in all_predictions):
1946
- # Return error plot if no valid predictions
1947
- fig = go.Figure()
1948
- fig.add_annotation(
1949
- text="No valid route predictions generated",
1950
- xref="paper", yref="paper",
1951
- x=0.5, y=0.5, xanchor='center', yanchor='middle',
1952
- showarrow=False, font_size=16
1953
- )
1954
- return fig
1955
-
1956
- fig = go.Figure()
1957
-
1958
- if enable_animation:
1959
- # Find max forecast length for consistent animation
1960
- max_hours = max(len(pred['route_forecast']) for pred in all_predictions if pred.get('route_forecast'))
1961
-
1962
- # Add genesis points for all routes (static background)
1963
- for i, prediction in enumerate(all_predictions):
1964
- if prediction.get('route_forecast'):
1965
- first_point = prediction['route_forecast'][0]
1966
- color = route_colors[i % len(route_colors)]
1967
-
1968
- fig.add_trace(
1969
- go.Scattergeo(
1970
- lon=[first_point['lon']],
1971
- lat=[first_point['lat']],
1972
- mode='markers',
1973
- marker=dict(
1974
- size=20,
1975
- color=color,
1976
- symbol='star',
1977
- line=dict(width=2, color='white')
1978
- ),
1979
- name=f'Route {prediction["route_id"]} Genesis',
1980
- showlegend=True,
1981
- hovertemplate=(
1982
- f"<b>Route {prediction['route_id']} Genesis</b><br>"
1983
- f"Region: {prediction['genesis_region']}<br>"
1984
- f"ONI: {prediction['oni_used']:.2f}<br>"
1985
- "<extra></extra>"
1986
- )
1987
- )
1988
- )
1989
-
1990
- # Create animation frames
1991
- frames = []
1992
-
1993
- for frame_idx in range(max_hours):
1994
- frame_data = []
1995
-
1996
- for i, prediction in enumerate(all_predictions):
1997
- route_data = prediction.get('route_forecast', [])
1998
- color = route_colors[i % len(route_colors)]
1999
-
2000
- if frame_idx < len(route_data):
2001
- # Get route points up to current frame
2002
- current_points = route_data[:frame_idx + 1]
2003
-
2004
- if current_points:
2005
- lats = [p['lat'] for p in current_points]
2006
- lons = [p['lon'] for p in current_points]
2007
- intensities = [p['intensity_kt'] for p in current_points]
2008
-
2009
- # Add route line
2010
- frame_data.append(
2011
- go.Scattergeo(
2012
- lon=lons,
2013
- lat=lats,
2014
- mode='lines+markers',
2015
- line=dict(color=color, width=3),
2016
- marker=dict(
2017
- size=[6 + max(0, int/15) for int in intensities],
2018
- color=color,
2019
- opacity=0.8
2020
- ),
2021
- name=f'Route {prediction["route_id"]}',
2022
- showlegend=False,
2023
- hovertemplate=(
2024
- f"<b>Route {prediction['route_id']}</b><br>"
2025
- f"Hour {current_points[-1]['hour']}<br>"
2026
- f"Intensity: {current_points[-1]['intensity_kt']:.0f} kt<br>"
2027
- f"Category: {current_points[-1]['category']}<br>"
2028
- "<extra></extra>"
2029
- )
2030
- )
2031
- )
2032
-
2033
- frames.append(go.Frame(
2034
- data=frame_data,
2035
- name=str(frame_idx),
2036
- layout=go.Layout(
2037
- title=f"Multi-Route Storm Development Animation - Hour {frame_idx * 6}"
2038
- )
2039
- ))
2040
-
2041
- fig.frames = frames
2042
-
2043
- # Add animation controls
2044
- fig.update_layout(
2045
- updatemenus=[{
2046
- "buttons": [
2047
- {
2048
- "args": [None, {"frame": {"duration": 800, "redraw": True},
2049
- "fromcurrent": True, "transition": {"duration": 300}}],
2050
- "label": "▶️ Play",
2051
- "method": "animate"
2052
- },
2053
- {
2054
- "args": [[None], {"frame": {"duration": 0, "redraw": True},
2055
- "mode": "immediate", "transition": {"duration": 0}}],
2056
- "label": "⏸️ Pause",
2057
- "method": "animate"
2058
- }
2059
- ],
2060
- "direction": "left",
2061
- "pad": {"r": 10, "t": 87},
2062
- "showactive": False,
2063
- "type": "buttons",
2064
- "x": 0.1,
2065
- "xanchor": "right",
2066
- "y": 0,
2067
- "yanchor": "top"
2068
- }],
2069
- sliders=[{
2070
- "active": 0,
2071
- "yanchor": "top",
2072
- "xanchor": "left",
2073
- "currentvalue": {
2074
- "font": {"size": 16},
2075
- "prefix": "Hour: ",
2076
- "visible": True,
2077
- "xanchor": "right"
2078
- },
2079
- "transition": {"duration": 300, "easing": "cubic-in-out"},
2080
- "pad": {"b": 10, "t": 50},
2081
- "len": 0.9,
2082
- "x": 0.1,
2083
- "y": 0,
2084
- "steps": [
2085
- {
2086
- "args": [[str(i)], {"frame": {"duration": 300, "redraw": True},
2087
- "mode": "immediate", "transition": {"duration": 300}}],
2088
- "label": f"H{i*6}",
2089
- "method": "animate"
2090
- }
2091
- for i in range(0, max_hours, max(1, max_hours//20))
2092
- ]
2093
- }]
2094
- )
2095
-
2096
- else:
2097
- # Static version showing all complete routes
2098
- for i, prediction in enumerate(all_predictions):
2099
- route_data = prediction.get('route_forecast', [])
2100
- color = route_colors[i % len(route_colors)]
2101
-
2102
- if route_data:
2103
- lats = [p['lat'] for p in route_data]
2104
- lons = [p['lon'] for p in route_data]
2105
- intensities = [p['intensity_kt'] for p in route_data]
2106
-
2107
- # Add complete route
2108
- fig.add_trace(
2109
- go.Scattergeo(
2110
- lon=lons,
2111
- lat=lats,
2112
- mode='lines+markers',
2113
- line=dict(color=color, width=3),
2114
- marker=dict(
2115
- size=[6 + max(0, int/15) for int in intensities],
2116
- color=color,
2117
- opacity=0.8
2118
- ),
2119
- name=f'Route {prediction["route_id"]} - {prediction["genesis_region"][:20]}...',
2120
- hovertemplate=(
2121
- f"<b>Route {prediction['route_id']}</b><br>"
2122
- f"Region: {prediction['genesis_region']}<br>"
2123
- f"ONI: {prediction['oni_used']:.2f}<br>"
2124
- "<extra></extra>"
2125
- )
2126
- )
2127
- )
2128
-
2129
- # Add genesis marker
2130
- fig.add_trace(
2131
- go.Scattergeo(
2132
- lon=[lons[0]],
2133
- lat=[lats[0]],
2134
- mode='markers',
2135
- marker=dict(
2136
- size=20,
2137
- color=color,
2138
- symbol='star',
2139
- line=dict(width=2, color='white')
2140
- ),
2141
- name=f'Route {prediction["route_id"]} Genesis',
2142
- showlegend=False
2143
- )
2144
- )
2145
-
2146
- # Update layout
2147
- fig.update_layout(
2148
- title="Multiple Route Storm Predictions" + (" (Animated)" if enable_animation else " (Static)"),
2149
- geo=dict(
2150
- projection_type="natural earth",
2151
- showland=True,
2152
- landcolor="LightGray",
2153
- showocean=True,
2154
- oceancolor="LightBlue",
2155
- showcoastlines=True,
2156
- coastlinecolor="Gray",
2157
- center=dict(lat=20, lon=140),
2158
- projection_scale=2.0
2159
- ),
2160
- height=800
2161
- )
2162
-
2163
- return fig
2164
-
2165
- def create_intensity_comparison(all_predictions):
2166
- """Create intensity comparison plot for multiple routes"""
2167
- fig = go.Figure()
2168
-
2169
- route_colors = ['#FF0066', '#00FF66', '#6600FF', '#FF6600', '#0066FF']
2170
-
2171
- for i, prediction in enumerate(all_predictions):
2172
- route_data = prediction.get('route_forecast', [])
2173
- if route_data:
2174
- hours = [p['hour'] for p in route_data]
2175
- intensities = [p['intensity_kt'] for p in route_data]
2176
- color = route_colors[i % len(route_colors)]
2177
-
2178
- fig.add_trace(
2179
- go.Scatter(
2180
- x=hours,
2181
- y=intensities,
2182
- mode='lines+markers',
2183
- line=dict(color=color, width=3),
2184
- marker=dict(size=6, color=color),
2185
- name=f'Route {prediction["route_id"]}',
2186
- hovertemplate=(
2187
- f"<b>Route {prediction['route_id']}</b><br>"
2188
- f"Hour: %{{x}}<br>"
2189
- f"Intensity: %{{y:.0f}} kt<br>"
2190
- "<extra></extra>"
2191
- )
2192
- )
2193
- )
2194
-
2195
- # Add category threshold lines
2196
- thresholds = [34, 64, 83, 96, 113, 137]
2197
- threshold_names = ['TS', 'C1', 'C2', 'C3', 'C4', 'C5']
2198
-
2199
- for thresh, name in zip(thresholds, threshold_names):
2200
- fig.add_hline(
2201
- y=thresh,
2202
- line_dash="dash",
2203
- line_color="gray",
2204
- annotation_text=name,
2205
- annotation_position="right"
2206
- )
2207
-
2208
- fig.update_layout(
2209
- title="Intensity Evolution Comparison - All Routes",
2210
- xaxis_title="Forecast Hour",
2211
- yaxis_title="Wind Speed (kt)",
2212
- height=400
2213
- )
2214
-
2215
- return fig
2216
-
2217
  # -----------------------------
2218
  # Regression Functions (Original)
2219
  # -----------------------------
@@ -2791,204 +2832,71 @@ def create_interface():
2791
  """
2792
  gr.Markdown(overview_text)
2793
 
2794
- with gr.Tab("🎯 Simple Multi-Route Prediction"):
2795
- gr.Markdown("## 🎯 Simple Storm Prediction - Just Month & ONI")
2796
- gr.Markdown("**Enter just the month and ONI value to see 5 different realistic storm development scenarios with prediction uncertainty**")
2797
 
2798
  with gr.Row():
2799
  with gr.Column(scale=1):
2800
- simple_pred_month = gr.Slider(
2801
  1, 12,
2802
- label="Prediction Month",
2803
  value=9,
2804
- info="Peak typhoon season: Jul-Oct"
 
2805
  )
2806
- simple_pred_oni = gr.Number(
2807
  label="ONI Value",
2808
  value=0.0,
2809
  info="El Niño (+) / La Niña (-) / Neutral (0)"
2810
  )
2811
- simple_enable_animation = gr.Checkbox(
2812
  label="Enable Animation",
2813
  value=True,
2814
- info="Watch storms develop over time"
2815
  )
2816
- simple_predict_btn = gr.Button("🌊 Generate 5 Route Predictions", variant="primary", size="lg")
2817
 
2818
  with gr.Column(scale=2):
2819
  gr.Markdown("### 🌊 What You'll Get:")
2820
  gr.Markdown("""
2821
- - **5 Different Routes**: Various genesis regions and development paths
2822
- - **Real-time Animation**: Watch all storms develop simultaneously
2823
- - **Intensity Tracking**: See how each route intensifies differently
2824
- - **Realistic Physics**: Environmental effects on each route
2825
- - **Prediction Errors**: Uncertainty estimates and confidence intervals
2826
- - **Comparative Analysis**: See best/worst case scenarios
2827
  """)
2828
 
2829
  with gr.Row():
2830
- simple_route_animation = gr.Plot(label="🗺️ Multi-Route Animation")
2831
-
2832
- with gr.Row():
2833
- simple_intensity_comparison = gr.Plot(label="📊 Intensity Evolution Comparison")
2834
 
2835
  with gr.Row():
2836
- simple_prediction_summary = gr.Textbox(label="📋 Prediction Summary with Error Analysis", lines=20)
2837
 
2838
- def run_simplified_prediction(month, oni, animation):
2839
  try:
2840
- # Generate multiple route predictions
2841
- all_predictions = generate_multiple_route_predictions(month, oni, num_routes=5)
2842
-
2843
- # Create multi-route animation
2844
- route_fig = create_multi_route_animation(all_predictions, animation)
2845
-
2846
- # Create intensity comparison
2847
- intensity_fig = create_intensity_comparison(all_predictions)
2848
-
2849
- # Generate summary text with error analysis
2850
- summary_text = f"""
2851
- SIMPLIFIED MULTI-ROUTE PREDICTION SUMMARY WITH ERROR ANALYSIS
2852
- {'='*65}
2853
-
2854
- INPUT CONDITIONS:
2855
- • Month: {month} {'(Peak Season)' if month in [7,8,9,10] else '(Off Season)'}
2856
- • ONI Value: {oni:.2f} {'(El Niño)' if oni > 0.5 else '(La Niña)' if oni < -0.5 else '(Neutral)'}
2857
-
2858
- ROUTE PREDICTIONS GENERATED: 5 scenarios
2859
-
2860
- ROUTE BREAKDOWN:
2861
- """
2862
-
2863
- all_max_intensities = []
2864
- all_final_intensities = []
2865
- genesis_regions = []
2866
 
2867
- for i, pred in enumerate(all_predictions):
2868
- route_data = pred.get('route_forecast', [])
2869
- if route_data:
2870
- max_intensity = max(p['intensity_kt'] for p in route_data)
2871
- final_intensity = route_data[-1]['intensity_kt']
2872
- max_category = max((categorize_typhoon_enhanced(p['intensity_kt']) for p in route_data),
2873
- key=lambda x: ['Tropical Depression', 'Tropical Storm', 'C1 Typhoon', 'C2 Typhoon', 'C3 Strong Typhoon', 'C4 Very Strong Typhoon', 'C5 Super Typhoon'].index(x))
2874
-
2875
- all_max_intensities.append(max_intensity)
2876
- all_final_intensities.append(final_intensity)
2877
- genesis_regions.append(pred['genesis_region'])
2878
-
2879
- summary_text += f"""
2880
- ROUTE {pred['route_id']}:
2881
- • Genesis: {pred['genesis_region']}
2882
- • ONI Used: {pred['oni_used']:.2f}
2883
- • Peak Intensity: {max_intensity:.0f} kt ({max_category})
2884
- • Final Intensity: {final_intensity:.0f} kt
2885
- • Duration: {len(route_data)} time steps ({len(route_data)*6} hours)
2886
- • Confidence: {pred.get('confidence_scores', {}).get('long_term', 0.5)*100:.0f}%
2887
- """
2888
 
2889
- # Enhanced scenario analysis with prediction errors
2890
- if all_max_intensities:
2891
- mean_max = np.mean(all_max_intensities)
2892
- std_max = np.std(all_max_intensities)
2893
- mean_final = np.mean(all_final_intensities)
2894
- std_final = np.std(all_final_intensities)
2895
-
2896
- # Calculate prediction uncertainty metrics
2897
- intensity_range = max(all_max_intensities) - min(all_max_intensities)
2898
- prediction_skill = max(0, 1 - (std_max / max(mean_max, 1)))
2899
-
2900
- # Environmental predictability
2901
- if month in [7, 8, 9]: # Peak season
2902
- environmental_predictability = 0.8
2903
- elif month in [10, 11, 6]: # Shoulder season
2904
- environmental_predictability = 0.6
2905
- else: # Off season
2906
- environmental_predictability = 0.4
2907
-
2908
- # ENSO influence on predictability
2909
- enso_predictability = 0.7 + 0.2 * min(abs(oni), 2.0) / 2.0
2910
-
2911
- # Overall prediction confidence
2912
- overall_confidence = (prediction_skill * 0.4 +
2913
- environmental_predictability * 0.3 +
2914
- enso_predictability * 0.3)
2915
-
2916
- summary_text += f"""
2917
-
2918
- PREDICTION ERROR & UNCERTAINTY ANALYSIS:
2919
- ==========================================
2920
-
2921
- INTENSITY STATISTICS:
2922
- • Peak Intensity Range: {min(all_max_intensities):.0f} - {max(all_max_intensities):.0f} kt
2923
- • Mean Peak Intensity: {mean_max:.0f} ± {std_max:.0f} kt
2924
- • Final Intensity Range: {min(all_final_intensities):.0f} - {max(all_final_intensities):.0f} kt
2925
- • Mean Final Intensity: {mean_final:.0f} ± {std_final:.0f} kt
2926
-
2927
- PREDICTION UNCERTAINTY METRICS:
2928
- • Intensity Spread: {intensity_range:.0f} kt (uncertainty range)
2929
- • Prediction Skill: {prediction_skill:.2f} (0=poor, 1=excellent)
2930
- • Environmental Predictability: {environmental_predictability:.2f}
2931
- • ENSO Influence Factor: {enso_predictability:.2f}
2932
- • Overall Confidence: {overall_confidence:.2f} ({overall_confidence*100:.0f}%)
2933
-
2934
- ERROR SOURCES & LIMITATIONS:
2935
- • Genesis Location Uncertainty: ±2-5° lat/lon
2936
- • Steering Flow Variability: ±15-25% track speed
2937
- • Intensity Development: ±20-40 kt at 72h forecast
2938
- • Environmental Interaction: Variable SST/shear effects
2939
- • Model Physics: Simplified compared to full NWP models
2940
-
2941
- INTERPRETATION GUIDE:
2942
- {"🔴 HIGH UNCERTAINTY" if overall_confidence < 0.5 else "🟡 MODERATE UNCERTAINTY" if overall_confidence < 0.7 else "🟢 GOOD CONFIDENCE"}
2943
- • Spread > 40 kt: High uncertainty scenario
2944
- • Spread 20-40 kt: Moderate uncertainty
2945
- • Spread < 20 kt: Good agreement between scenarios
2946
-
2947
- FORECAST SKILL BY TIME:
2948
- • 0-24h: ~85% position accuracy, ~75% intensity
2949
- • 24-48h: ~75% position accuracy, ~65% intensity
2950
- • 48-72h: ~65% position accuracy, ~55% intensity
2951
- • 72h+: ~50% position accuracy, ~45% intensity
2952
-
2953
- GENESIS REGION DIVERSITY:
2954
- • Primary Regions: {len(set(genesis_regions))} different zones
2955
- • Most Active: {max(set(genesis_regions), key=genesis_regions.count)}
2956
- • Least Represented: {min(set(genesis_regions), key=genesis_regions.count)}
2957
-
2958
- SEASONAL CONTEXT:
2959
- {"Peak season - higher confidence in development patterns" if month in [7,8,9,10] else "Off-season - increased uncertainty in storm behavior"}
2960
-
2961
- ENSO INFLUENCE:
2962
- {"Strong El Niño suppression expected" if oni > 1.0 else "Moderate El Niño effects" if oni > 0.5 else "Strong La Niña enhancement likely" if oni < -1.0 else "Moderate La Niña effects" if oni < -0.5 else "Neutral ENSO - average conditions"}
2963
-
2964
- ANIMATION FEATURES:
2965
- {"✅ Real-time development animation enabled" if animation else "📊 Static multi-route comparison"}
2966
- ✅ 5 simultaneous storm tracks with uncertainty
2967
- ✅ Color-coded intensity evolution
2968
- ✅ Realistic genesis locations and physics
2969
- ✅ Environmental coupling effects
2970
- ✅ Confidence-weighted predictions
2971
-
2972
- ⚠️ IMPORTANT DISCLAIMERS:
2973
- • These are statistical predictions, not operational forecasts
2974
- • Actual storms may behave differently due to unmodeled factors
2975
- • Use for research/planning only, not for emergency decisions
2976
- • Consult official meteorological services for real forecasts
2977
- • Model simplified compared to operational NWP systems
2978
- """
2979
 
2980
- return route_fig, intensity_fig, summary_text
2981
 
2982
  except Exception as e:
2983
  import traceback
2984
- error_msg = f"Prediction failed: {str(e)}\n\nDetails:\n{traceback.format_exc()}"
2985
  logging.error(error_msg)
2986
- return None, None, error_msg
2987
 
2988
- simple_predict_btn.click(
2989
- fn=run_simplified_prediction,
2990
- inputs=[simple_pred_month, simple_pred_oni, simple_enable_animation],
2991
- outputs=[simple_route_animation, simple_intensity_comparison, simple_prediction_summary]
2992
  )
2993
 
2994
  with gr.Tab("🔬 Advanced ML Clustering"):
 
801
  return 'Tropical Storm', '#0000FF' # Blue
802
  return 'Tropical Depression', '#808080' # Gray
803
 
804
+ # -----------------------------
805
+ # FIXED: Genesis Potential Index (GPI) Based Prediction System
806
+ # -----------------------------
807
+
808
+ def calculate_genesis_potential_index(sst, rh, vorticity, wind_shear, lat, lon, month, oni_value):
809
+ """
810
+ Calculate Genesis Potential Index based on environmental parameters
811
+ Following Emanuel and Nolan (2004) formulation with modifications for monthly predictions
812
+ """
813
+ try:
814
+ # Base environmental parameters
815
+
816
+ # SST factor - optimal range 26-30°C
817
+ sst_factor = max(0, (sst - 26.5) / 4.0) if sst > 26.5 else 0
818
+
819
+ # Humidity factor - mid-level relative humidity (600 hPa)
820
+ rh_factor = max(0, (rh - 40) / 50.0) # Normalized 40-90%
821
+
822
+ # Vorticity factor - low-level absolute vorticity (850 hPa)
823
+ vort_factor = max(0, min(vorticity / 5e-5, 3.0)) # Cap at reasonable max
824
+
825
+ # Wind shear factor - vertical wind shear (inverse relationship)
826
+ shear_factor = max(0, (20 - wind_shear) / 15.0) if wind_shear < 20 else 0
827
+
828
+ # Coriolis factor - latitude dependency
829
+ coriolis_factor = max(0, min(abs(lat) / 20.0, 1.0)) if abs(lat) > 5 else 0
830
+
831
+ # Seasonal factor
832
+ seasonal_weights = {
833
+ 1: 0.3, 2: 0.2, 3: 0.4, 4: 0.6, 5: 0.8, 6: 1.0,
834
+ 7: 1.2, 8: 1.4, 9: 1.5, 10: 1.3, 11: 0.9, 12: 0.5
835
+ }
836
+ seasonal_factor = seasonal_weights.get(month, 1.0)
837
+
838
+ # ENSO modulation
839
+ if oni_value > 0.5: # El Niño
840
+ enso_factor = 0.6 if lon > 140 else 0.8 # Suppress in WP
841
+ elif oni_value < -0.5: # La Niña
842
+ enso_factor = 1.4 if lon > 140 else 1.1 # Enhance in WP
843
+ else: # Neutral
844
+ enso_factor = 1.0
845
+
846
+ # Regional modulation (Western Pacific specifics)
847
+ if 10 <= lat <= 25 and 120 <= lon <= 160: # Main Development Region
848
+ regional_factor = 1.3
849
+ elif 5 <= lat <= 15 and 130 <= lon <= 150: # Prime genesis zone
850
+ regional_factor = 1.5
851
+ else:
852
+ regional_factor = 0.8
853
+
854
+ # Calculate GPI
855
+ gpi = (sst_factor * rh_factor * vort_factor * shear_factor *
856
+ coriolis_factor * seasonal_factor * enso_factor * regional_factor)
857
+
858
+ return max(0, min(gpi, 5.0)) # Cap at reasonable maximum
859
+
860
+ except Exception as e:
861
+ logging.error(f"Error calculating GPI: {e}")
862
+ return 0.0
863
+
864
+ def get_environmental_conditions(lat, lon, month, oni_value):
865
+ """
866
+ Get realistic environmental conditions for a given location and time
867
+ Based on climatological patterns and ENSO modulation
868
+ """
869
+ try:
870
+ # Base SST calculation (climatological)
871
+ base_sst = 28.5 - 0.15 * abs(lat - 15) # Peak at 15°N
872
+ seasonal_sst_adj = 2.0 * np.cos(2 * np.pi * (month - 9) / 12) # Peak in Sep
873
+ enso_sst_adj = oni_value * 0.8 if lon > 140 else oni_value * 0.4
874
+ sst = base_sst + seasonal_sst_adj + enso_sst_adj
875
+
876
+ # Relative humidity (600 hPa)
877
+ base_rh = 75 - 0.5 * abs(lat - 12) # Peak around 12°N
878
+ seasonal_rh_adj = 10 * np.cos(2 * np.pi * (month - 8) / 12) # Peak in Aug
879
+ monsoon_effect = 5 if 100 <= lon <= 120 and month in [6,7,8,9] else 0
880
+ rh = max(40, min(90, base_rh + seasonal_rh_adj + monsoon_effect))
881
+
882
+ # Low-level vorticity (850 hPa)
883
+ base_vort = 2e-5 * (1 + 0.1 * np.sin(2 * np.pi * lat / 30))
884
+ seasonal_vort_adj = 1e-5 * np.cos(2 * np.pi * (month - 8) / 12)
885
+ itcz_effect = 1.5e-5 if 5 <= lat <= 15 else 0
886
+ vorticity = max(0, base_vort + seasonal_vort_adj + itcz_effect)
887
+
888
+ # Vertical wind shear (200-850 hPa)
889
+ base_shear = 8 + 0.3 * abs(lat - 20) # Lower near 20°N
890
+ seasonal_shear_adj = 4 * np.cos(2 * np.pi * (month - 2) / 12) # Low in Aug-Sep
891
+ enso_shear_adj = oni_value * 3 if lon > 140 else 0 # El Niño increases shear
892
+ wind_shear = max(2, base_shear + seasonal_shear_adj + enso_shear_adj)
893
+
894
+ return {
895
+ 'sst': sst,
896
+ 'relative_humidity': rh,
897
+ 'vorticity': vorticity,
898
+ 'wind_shear': wind_shear
899
+ }
900
+
901
+ except Exception as e:
902
+ logging.error(f"Error getting environmental conditions: {e}")
903
+ return {
904
+ 'sst': 28.0,
905
+ 'relative_humidity': 70.0,
906
+ 'vorticity': 2e-5,
907
+ 'wind_shear': 10.0
908
+ }
909
+
910
+ def generate_genesis_prediction_monthly(month, oni_value, year=2025):
911
+ """
912
+ Generate realistic typhoon genesis prediction for a given month using GPI
913
+ Returns day-by-day genesis potential and storm development scenarios
914
+ """
915
+ try:
916
+ logging.info(f"Generating GPI-based prediction for month {month}, ONI {oni_value}")
917
+
918
+ # Define the Western Pacific domain
919
+ lat_range = np.arange(5, 35, 2.5) # 5°N to 35°N
920
+ lon_range = np.arange(110, 180, 2.5) # 110°E to 180°E
921
+
922
+ # Number of days in the month
923
+ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]
924
+ if month == 2 and year % 4 == 0: # Leap year
925
+ days_in_month = 29
926
+
927
+ # Daily GPI evolution
928
+ daily_gpi_maps = []
929
+ genesis_events = []
930
+
931
+ for day in range(1, days_in_month + 1):
932
+ # Calculate GPI for each grid point
933
+ gpi_field = np.zeros((len(lat_range), len(lon_range)))
934
+
935
+ for i, lat in enumerate(lat_range):
936
+ for j, lon in enumerate(lon_range):
937
+ # Get environmental conditions
938
+ env_conditions = get_environmental_conditions(lat, lon, month, oni_value)
939
+
940
+ # Add daily variability
941
+ daily_variation = 0.1 * np.sin(2 * np.pi * day / 30) + np.random.normal(0, 0.05)
942
+
943
+ # Calculate GPI
944
+ gpi = calculate_genesis_potential_index(
945
+ sst=env_conditions['sst'] + daily_variation,
946
+ rh=env_conditions['relative_humidity'],
947
+ vorticity=env_conditions['vorticity'],
948
+ wind_shear=env_conditions['wind_shear'],
949
+ lat=lat,
950
+ lon=lon,
951
+ month=month,
952
+ oni_value=oni_value
953
+ )
954
+
955
+ gpi_field[i, j] = gpi
956
+
957
+ daily_gpi_maps.append({
958
+ 'day': day,
959
+ 'gpi_field': gpi_field,
960
+ 'lat_range': lat_range,
961
+ 'lon_range': lon_range
962
+ })
963
+
964
+ # Check for genesis events (GPI > threshold)
965
+ genesis_threshold = 1.5 # Adjusted threshold
966
+ if np.max(gpi_field) > genesis_threshold:
967
+ # Find peak genesis locations
968
+ peak_indices = np.where(gpi_field > genesis_threshold)
969
+ if len(peak_indices[0]) > 0:
970
+ # Select strongest genesis point
971
+ max_idx = np.argmax(gpi_field)
972
+ max_i, max_j = np.unravel_index(max_idx, gpi_field.shape)
973
+
974
+ genesis_lat = lat_range[max_i]
975
+ genesis_lon = lon_range[max_j]
976
+ genesis_gpi = gpi_field[max_i, max_j]
977
+
978
+ # Determine probability of actual genesis
979
+ genesis_prob = min(0.8, genesis_gpi / 3.0)
980
+
981
+ if np.random.random() < genesis_prob:
982
+ genesis_events.append({
983
+ 'day': day,
984
+ 'lat': genesis_lat,
985
+ 'lon': genesis_lon,
986
+ 'gpi': genesis_gpi,
987
+ 'probability': genesis_prob,
988
+ 'date': f"{year}-{month:02d}-{day:02d}"
989
+ })
990
+
991
+ # Generate storm tracks for genesis events
992
+ storm_predictions = []
993
+ for i, genesis in enumerate(genesis_events):
994
+ storm_track = generate_storm_track_from_genesis(
995
+ genesis['lat'],
996
+ genesis['lon'],
997
+ genesis['day'],
998
+ month,
999
+ oni_value,
1000
+ storm_id=i+1
1001
+ )
1002
+
1003
+ if storm_track:
1004
+ storm_predictions.append({
1005
+ 'storm_id': i + 1,
1006
+ 'genesis_event': genesis,
1007
+ 'track': storm_track,
1008
+ 'uncertainty': calculate_track_uncertainty(storm_track)
1009
+ })
1010
+
1011
+ return {
1012
+ 'month': month,
1013
+ 'year': year,
1014
+ 'oni_value': oni_value,
1015
+ 'daily_gpi_maps': daily_gpi_maps,
1016
+ 'genesis_events': genesis_events,
1017
+ 'storm_predictions': storm_predictions,
1018
+ 'summary': {
1019
+ 'total_genesis_events': len(genesis_events),
1020
+ 'total_storm_predictions': len(storm_predictions),
1021
+ 'peak_gpi_day': max(daily_gpi_maps, key=lambda x: np.max(x['gpi_field']))['day'],
1022
+ 'peak_gpi_value': max(np.max(day_data['gpi_field']) for day_data in daily_gpi_maps)
1023
+ }
1024
+ }
1025
+
1026
+ except Exception as e:
1027
+ logging.error(f"Error in genesis prediction: {e}")
1028
+ import traceback
1029
+ traceback.print_exc()
1030
+ return {
1031
+ 'error': str(e),
1032
+ 'month': month,
1033
+ 'oni_value': oni_value,
1034
+ 'storm_predictions': []
1035
+ }
1036
+
1037
+ def generate_storm_track_from_genesis(genesis_lat, genesis_lon, genesis_day, month, oni_value, storm_id=1):
1038
+ """
1039
+ Generate a realistic storm track from a genesis location
1040
+ """
1041
+ try:
1042
+ track_points = []
1043
+ current_lat = genesis_lat
1044
+ current_lon = genesis_lon
1045
+ current_intensity = 25 # Start as TD
1046
+
1047
+ # Track duration (3-10 days typically)
1048
+ track_duration_hours = np.random.randint(72, 240)
1049
+
1050
+ for hour in range(0, track_duration_hours + 6, 6):
1051
+ # Calculate storm motion
1052
+ # Base motion patterns for Western Pacific
1053
+ if current_lat < 20: # Low latitude - westward motion
1054
+ lat_speed = 0.1 + np.random.normal(0, 0.05) # Slight poleward
1055
+ lon_speed = -0.3 + np.random.normal(0, 0.1) # Westward
1056
+ elif current_lat < 25: # Mid latitude - WNW motion
1057
+ lat_speed = 0.15 + np.random.normal(0, 0.05)
1058
+ lon_speed = -0.2 + np.random.normal(0, 0.1)
1059
+ else: # High latitude - recurvature
1060
+ lat_speed = 0.2 + np.random.normal(0, 0.05)
1061
+ lon_speed = 0.1 + np.random.normal(0, 0.1) # Eastward
1062
+
1063
+ # ENSO effects on motion
1064
+ if oni_value > 0.5: # El Niño - more eastward
1065
+ lon_speed += 0.05
1066
+ elif oni_value < -0.5: # La Niña - more westward
1067
+ lon_speed -= 0.05
1068
+
1069
+ # Update position
1070
+ current_lat += lat_speed
1071
+ current_lon += lon_speed
1072
+
1073
+ # Intensity evolution
1074
+ # Get environmental conditions for intensity change
1075
+ env_conditions = get_environmental_conditions(current_lat, current_lon, month, oni_value)
1076
+
1077
+ # Intensity change factors
1078
+ sst_factor = max(0, env_conditions['sst'] - 26.5)
1079
+ shear_factor = max(0, (15 - env_conditions['wind_shear']) / 10)
1080
+
1081
+ # Basic intensity change
1082
+ if hour < 48: # Development phase
1083
+ intensity_change = 3 + sst_factor + shear_factor + np.random.normal(0, 2)
1084
+ elif hour < 120: # Mature phase
1085
+ intensity_change = 1 + sst_factor * 0.5 + np.random.normal(0, 1.5)
1086
+ else: # Weakening phase
1087
+ intensity_change = -2 + sst_factor * 0.3 + np.random.normal(0, 1)
1088
+
1089
+ # Environmental limits
1090
+ if current_lat > 30: # Cool waters
1091
+ intensity_change -= 5
1092
+ if current_lon < 120: # Land interaction
1093
+ intensity_change -= 8
1094
+
1095
+ current_intensity += intensity_change
1096
+ current_intensity = max(15, min(180, current_intensity)) # Realistic bounds
1097
+
1098
+ # Calculate pressure
1099
+ pressure = max(900, 1013 - (current_intensity - 25) * 0.9)
1100
+
1101
+ # Add uncertainty
1102
+ position_uncertainty = 0.5 + (hour / 120) * 1.5 # Growing uncertainty
1103
+ intensity_uncertainty = 5 + (hour / 120) * 15
1104
+
1105
+ track_points.append({
1106
+ 'hour': hour,
1107
+ 'day': genesis_day + hour / 24.0,
1108
+ 'lat': current_lat,
1109
+ 'lon': current_lon,
1110
+ 'intensity': current_intensity,
1111
+ 'pressure': pressure,
1112
+ 'category': categorize_typhoon_enhanced(current_intensity),
1113
+ 'position_uncertainty': position_uncertainty,
1114
+ 'intensity_uncertainty': intensity_uncertainty
1115
+ })
1116
+
1117
+ # Stop if storm moves too far or weakens significantly
1118
+ if current_lat > 40 or current_lat < 0 or current_lon < 100 or current_intensity < 20:
1119
+ break
1120
+
1121
+ return track_points
1122
+
1123
+ except Exception as e:
1124
+ logging.error(f"Error generating storm track: {e}")
1125
+ return None
1126
+
1127
+ def calculate_track_uncertainty(track_points):
1128
+ """Calculate uncertainty metrics for a storm track"""
1129
+ if not track_points:
1130
+ return {'position': 0, 'intensity': 0}
1131
+
1132
+ # Position uncertainty grows with time
1133
+ position_uncertainty = [point['position_uncertainty'] for point in track_points]
1134
+
1135
+ # Intensity uncertainty
1136
+ intensity_uncertainty = [point['intensity_uncertainty'] for point in track_points]
1137
+
1138
+ return {
1139
+ 'position_mean': np.mean(position_uncertainty),
1140
+ 'position_max': np.max(position_uncertainty),
1141
+ 'intensity_mean': np.mean(intensity_uncertainty),
1142
+ 'intensity_max': np.max(intensity_uncertainty),
1143
+ 'track_length': len(track_points)
1144
+ }
1145
+
1146
+ def create_genesis_animation(prediction_data, enable_animation=True):
1147
+ """
1148
+ Create animation showing daily genesis potential and storm development
1149
+ """
1150
+ try:
1151
+ if 'daily_gpi_maps' not in prediction_data or not prediction_data['daily_gpi_maps']:
1152
+ return create_error_plot("No GPI data available for animation")
1153
+
1154
+ daily_maps = prediction_data['daily_gpi_maps']
1155
+ storm_predictions = prediction_data.get('storm_predictions', [])
1156
+ month = prediction_data['month']
1157
+ oni_value = prediction_data['oni_value']
1158
+
1159
+ fig = go.Figure()
1160
+
1161
+ if enable_animation and len(daily_maps) > 1:
1162
+ # Create animated version
1163
+ frames = []
1164
+
1165
+ for day_idx, day_data in enumerate(daily_maps):
1166
+ day = day_data['day']
1167
+ gpi_field = day_data['gpi_field']
1168
+ lat_range = day_data['lat_range']
1169
+ lon_range = day_data['lon_range']
1170
+
1171
+ frame_data = []
1172
+
1173
+ # Add GPI contour
1174
+ frame_data.append(
1175
+ go.Contour(
1176
+ z=gpi_field,
1177
+ x=lon_range,
1178
+ y=lat_range,
1179
+ colorscale='Viridis',
1180
+ showscale=True,
1181
+ colorbar=dict(title="Genesis Potential Index"),
1182
+ name=f'GPI Day {day}',
1183
+ hovertemplate=(
1184
+ 'Longitude: %{x:.1f}°E<br>'
1185
+ 'Latitude: %{y:.1f}°N<br>'
1186
+ 'GPI: %{z:.2f}<br>'
1187
+ f'Day: {day}<br>'
1188
+ '<extra></extra>'
1189
+ )
1190
+ )
1191
+ )
1192
+
1193
+ # Add storm tracks up to current day
1194
+ for storm in storm_predictions:
1195
+ track = storm['track']
1196
+ # Only show track points up to current day
1197
+ current_track = [pt for pt in track if pt['day'] <= day]
1198
+
1199
+ if current_track:
1200
+ lats = [pt['lat'] for pt in current_track]
1201
+ lons = [pt['lon'] for pt in current_track]
1202
+ intensities = [pt['intensity'] for pt in current_track]
1203
+
1204
+ # Storm track
1205
+ frame_data.append(
1206
+ go.Scatter(
1207
+ x=lons,
1208
+ y=lats,
1209
+ mode='lines+markers',
1210
+ line=dict(color='red', width=3),
1211
+ marker=dict(
1212
+ size=[max(4, int/10) for int in intensities],
1213
+ color='red'
1214
+ ),
1215
+ name=f'Storm {storm["storm_id"]}',
1216
+ hovertemplate=(
1217
+ f'Storm {storm["storm_id"]}<br>'
1218
+ 'Longitude: %{x:.1f}°E<br>'
1219
+ 'Latitude: %{y:.1f}°N<br>'
1220
+ '<extra></extra>'
1221
+ ),
1222
+ showlegend=(day_idx == 0)
1223
+ )
1224
+ )
1225
+
1226
+ # Current position marker
1227
+ if current_track:
1228
+ current_pos = current_track[-1]
1229
+ frame_data.append(
1230
+ go.Scatter(
1231
+ x=[current_pos['lon']],
1232
+ y=[current_pos['lat']],
1233
+ mode='markers',
1234
+ marker=dict(
1235
+ size=15,
1236
+ color='yellow',
1237
+ symbol='star',
1238
+ line=dict(width=2, color='black')
1239
+ ),
1240
+ name=f'Storm {storm["storm_id"]} Current',
1241
+ showlegend=False,
1242
+ hovertemplate=(
1243
+ f'Storm {storm["storm_id"]} - Day {day:.1f}<br>'
1244
+ f'Intensity: {current_pos["intensity"]:.0f} kt<br>'
1245
+ f'Category: {current_pos["category"]}<br>'
1246
+ f'Pressure: {current_pos["pressure"]:.0f} hPa<br>'
1247
+ '<extra></extra>'
1248
+ )
1249
+ )
1250
+ )
1251
+
1252
+ frames.append(go.Frame(
1253
+ data=frame_data,
1254
+ name=str(day),
1255
+ layout=go.Layout(
1256
+ title=f"Genesis Potential & Storm Development - Month {month}, Day {day}<br>ONI: {oni_value:.2f}"
1257
+ )
1258
+ ))
1259
+
1260
+ fig.frames = frames
1261
+
1262
+ # Set initial frame
1263
+ if frames:
1264
+ fig.add_traces(frames[0].data)
1265
+
1266
+ # Add animation controls
1267
+ fig.update_layout(
1268
+ updatemenus=[{
1269
+ "buttons": [
1270
+ {
1271
+ "args": [None, {"frame": {"duration": 1000, "redraw": True},
1272
+ "fromcurrent": True, "transition": {"duration": 300}}],
1273
+ "label": "▶️ Play",
1274
+ "method": "animate"
1275
+ },
1276
+ {
1277
+ "args": [[None], {"frame": {"duration": 0, "redraw": True},
1278
+ "mode": "immediate", "transition": {"duration": 0}}],
1279
+ "label": "⏸️ Pause",
1280
+ "method": "animate"
1281
+ }
1282
+ ],
1283
+ "direction": "left",
1284
+ "pad": {"r": 10, "t": 87},
1285
+ "showactive": False,
1286
+ "type": "buttons",
1287
+ "x": 0.1,
1288
+ "xanchor": "right",
1289
+ "y": 0,
1290
+ "yanchor": "top"
1291
+ }],
1292
+ sliders=[{
1293
+ "active": 0,
1294
+ "yanchor": "top",
1295
+ "xanchor": "left",
1296
+ "currentvalue": {
1297
+ "font": {"size": 16},
1298
+ "prefix": "Day: ",
1299
+ "visible": True,
1300
+ "xanchor": "right"
1301
+ },
1302
+ "transition": {"duration": 300, "easing": "cubic-in-out"},
1303
+ "pad": {"b": 10, "t": 50},
1304
+ "len": 0.9,
1305
+ "x": 0.1,
1306
+ "y": 0,
1307
+ "steps": [
1308
+ {
1309
+ "args": [[str(day_data['day'])], {"frame": {"duration": 300, "redraw": True},
1310
+ "mode": "immediate", "transition": {"duration": 300}}],
1311
+ "label": f"D{day_data['day']}",
1312
+ "method": "animate"
1313
+ }
1314
+ for day_data in daily_maps[::max(1, len(daily_maps)//15)]
1315
+ ]
1316
+ }]
1317
+ )
1318
+
1319
+ else:
1320
+ # Static version showing final day
1321
+ final_day = daily_maps[-1]
1322
+ gpi_field = final_day['gpi_field']
1323
+ lat_range = final_day['lat_range']
1324
+ lon_range = final_day['lon_range']
1325
+
1326
+ # Add GPI contour
1327
+ fig.add_trace(
1328
+ go.Contour(
1329
+ z=gpi_field,
1330
+ x=lon_range,
1331
+ y=lat_range,
1332
+ colorscale='Viridis',
1333
+ showscale=True,
1334
+ colorbar=dict(title="Genesis Potential Index"),
1335
+ name='Genesis Potential',
1336
+ hovertemplate=(
1337
+ 'Longitude: %{x:.1f}°E<br>'
1338
+ 'Latitude: %{y:.1f}°N<br>'
1339
+ 'GPI: %{z:.2f}<br>'
1340
+ '<extra></extra>'
1341
+ )
1342
+ )
1343
+ )
1344
+
1345
+ # Add complete storm tracks
1346
+ for storm in storm_predictions:
1347
+ track = storm['track']
1348
+ if track:
1349
+ lats = [pt['lat'] for pt in track]
1350
+ lons = [pt['lon'] for pt in track]
1351
+ intensities = [pt['intensity'] for pt in track]
1352
+
1353
+ fig.add_trace(
1354
+ go.Scatter(
1355
+ x=lons,
1356
+ y=lats,
1357
+ mode='lines+markers',
1358
+ line=dict(color='red', width=3),
1359
+ marker=dict(
1360
+ size=[max(4, int/10) for int in intensities],
1361
+ color=intensities,
1362
+ colorscale='Reds',
1363
+ showscale=False
1364
+ ),
1365
+ name=f'Storm {storm["storm_id"]}',
1366
+ hovertemplate=(
1367
+ f'Storm {storm["storm_id"]}<br>'
1368
+ 'Longitude: %{x:.1f}°E<br>'
1369
+ 'Latitude: %{y:.1f}°N<br>'
1370
+ '<extra></extra>'
1371
+ )
1372
+ )
1373
+ )
1374
+
1375
+ # Genesis marker
1376
+ fig.add_trace(
1377
+ go.Scatter(
1378
+ x=[lons[0]],
1379
+ y=[lats[0]],
1380
+ mode='markers',
1381
+ marker=dict(
1382
+ size=20,
1383
+ color='yellow',
1384
+ symbol='star',
1385
+ line=dict(width=2, color='black')
1386
+ ),
1387
+ name=f'Genesis {storm["storm_id"]}',
1388
+ showlegend=False
1389
+ )
1390
+ )
1391
+
1392
+ # Update layout
1393
+ fig.update_layout(
1394
+ title=f"Typhoon Genesis Prediction - Month {month} (ONI: {oni_value:.2f})" +
1395
+ (" - Animated" if enable_animation else ""),
1396
+ xaxis_title="Longitude (°E)",
1397
+ yaxis_title="Latitude (°N)",
1398
+ width=1000,
1399
+ height=700,
1400
+ showlegend=True
1401
+ )
1402
+
1403
+ # Set map bounds
1404
+ fig.update_xaxes(range=[110, 180])
1405
+ fig.update_yaxes(range=[5, 35])
1406
+
1407
+ return fig
1408
+
1409
+ except Exception as e:
1410
+ logging.error(f"Error creating genesis animation: {e}")
1411
+ return create_error_plot(f"Animation error: {str(e)}")
1412
+
1413
+ def create_error_plot(error_message):
1414
+ """Create a simple error plot"""
1415
+ fig = go.Figure()
1416
+ fig.add_annotation(
1417
+ text=error_message,
1418
+ xref="paper", yref="paper",
1419
+ x=0.5, y=0.5, xanchor='center', yanchor='middle',
1420
+ showarrow=False, font_size=16
1421
+ )
1422
+ fig.update_layout(title="Error in Visualization")
1423
+ return fig
1424
+
1425
+ def create_prediction_summary(prediction_data):
1426
+ """Create a comprehensive summary of the prediction"""
1427
+ try:
1428
+ if 'error' in prediction_data:
1429
+ return f"Error in prediction: {prediction_data['error']}"
1430
+
1431
+ month = prediction_data['month']
1432
+ oni_value = prediction_data['oni_value']
1433
+ summary = prediction_data.get('summary', {})
1434
+ genesis_events = prediction_data.get('genesis_events', [])
1435
+ storm_predictions = prediction_data.get('storm_predictions', [])
1436
+
1437
+ month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
1438
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
1439
+ month_name = month_names[month - 1]
1440
+
1441
+ summary_text = f"""
1442
+ TYPHOON GENESIS PREDICTION SUMMARY - {month_name.upper()} 2025
1443
+ {'='*70}
1444
+
1445
+ 🌊 ENVIRONMENTAL CONDITIONS:
1446
+ • Month: {month_name} (Month {month})
1447
+ • ONI Value: {oni_value:.2f} {'(El Niño)' if oni_value > 0.5 else '(La Niña)' if oni_value < -0.5 else '(Neutral)'}
1448
+ • Season Phase: {'Peak Season' if month in [7,8,9,10] else 'Off Season' if month in [1,2,3,4,11,12] else 'Transition Season'}
1449
+
1450
+ 📊 GENESIS POTENTIAL ANALYSIS:
1451
+ • Peak GPI Day: Day {summary.get('peak_gpi_day', 'Unknown')}
1452
+ • Peak GPI Value: {summary.get('peak_gpi_value', 0):.2f}
1453
+ • Total Genesis Events: {summary.get('total_genesis_events', 0)}
1454
+ • Storm Development Success: {summary.get('total_storm_predictions', 0)}/{summary.get('total_genesis_events', 0)} events
1455
+
1456
+ 🎯 GENESIS EVENTS BREAKDOWN:
1457
+ """
1458
+
1459
+ if genesis_events:
1460
+ for i, event in enumerate(genesis_events, 1):
1461
+ summary_text += f"""
1462
+ Event {i}:
1463
+ • Date: {event['date']}
1464
+ • Location: {event['lat']:.1f}°N, {event['lon']:.1f}°E
1465
+ • GPI Value: {event['gpi']:.2f}
1466
+ • Genesis Probability: {event['probability']*100:.0f}%
1467
+ """
1468
+ else:
1469
+ summary_text += "\n• No significant genesis events predicted for this month\n"
1470
+
1471
+ summary_text += f"""
1472
+
1473
+ 🌪️ STORM TRACK PREDICTIONS:
1474
+ """
1475
+
1476
+ if storm_predictions:
1477
+ for storm in storm_predictions:
1478
+ track = storm['track']
1479
+ if track:
1480
+ genesis = storm['genesis_event']
1481
+ max_intensity = max(pt['intensity'] for pt in track)
1482
+ max_category = categorize_typhoon_enhanced(max_intensity)
1483
+ track_duration = len(track) * 6 # hours
1484
+ final_pos = track[-1]
1485
+
1486
+ summary_text += f"""
1487
+ Storm {storm['storm_id']}:
1488
+ • Genesis: Day {genesis['day']}, {genesis['lat']:.1f}°N {genesis['lon']:.1f}°E
1489
+ • Peak Intensity: {max_intensity:.0f} kt ({max_category})
1490
+ • Track Duration: {track_duration} hours ({track_duration/24:.1f} days)
1491
+ • Final Position: {final_pos['lat']:.1f}°N, {final_pos['lon']:.1f}°E
1492
+ • Uncertainty: ±{storm['uncertainty']['position_mean']:.1f}° position, ±{storm['uncertainty']['intensity_mean']:.0f} kt intensity
1493
+ """
1494
+ else:
1495
+ summary_text += "\n• No storm development predicted from genesis events\n"
1496
+
1497
+ # Add climatological context
1498
+ summary_text += f"""
1499
+
1500
+ 📈 CLIMATOLOGICAL CONTEXT:
1501
+ • {month_name} Typical Activity: {'Very High' if month in [8,9] else 'High' if month in [7,10] else 'Moderate' if month in [6,11] else 'Low'}
1502
+ • ENSO Influence: {'Strong suppression expected' if oni_value > 1.0 else 'Moderate suppression' if oni_value > 0.5 else 'Strong enhancement likely' if oni_value < -1.0 else 'Moderate enhancement' if oni_value < -0.5 else 'Near-normal activity'}
1503
+ • Regional Focus: Western Pacific Main Development Region (10-25°N, 120-160°E)
1504
+
1505
+ 🔧 METHODOLOGY DETAILS:
1506
+ • Genesis Potential Index: Emanuel & Nolan (2004) formulation
1507
+ • Environmental Factors: SST, humidity, vorticity, wind shear, Coriolis effect
1508
+ • Temporal Resolution: Daily evolution throughout month
1509
+ • Spatial Resolution: 2.5° grid spacing
1510
+ • ENSO Modulation: Integrated ONI effects on environmental parameters
1511
+ • Track Prediction: Physics-based storm motion and intensity evolution
1512
+
1513
+ ⚠️ UNCERTAINTY & LIMITATIONS:
1514
+ • Genesis timing: ±2-3 days typical uncertainty
1515
+ • Track position: Growing uncertainty with time (±0.5° to ±2°)
1516
+ • Intensity prediction: ±5-15 kt uncertainty range
1517
+ • Environmental assumptions: Based on climatological patterns
1518
+ • Model limitations: Simplified compared to operational NWP systems
1519
+
1520
+ 🎯 FORECAST CONFIDENCE:
1521
+ • Genesis Location: {'High' if summary.get('peak_gpi_value', 0) > 2 else 'Moderate' if summary.get('peak_gpi_value', 0) > 1 else 'Low'}
1522
+ • Genesis Timing: {'High' if month in [7,8,9] else 'Moderate' if month in [6,10] else 'Low'}
1523
+ • Track Prediction: Moderate (physics-based motion patterns)
1524
+ • Intensity Evolution: Moderate (environmental constraints applied)
1525
+
1526
+ 📋 OPERATIONAL IMPLICATIONS:
1527
+ • Monitor Days {', '.join([str(event['day']) for event in genesis_events[:3]])} for potential development
1528
+ • Focus regions: {', '.join([f"{event['lat']:.0f}°N {event['lon']:.0f}°E" for event in genesis_events[:3]])}
1529
+ • Preparedness level: {'High' if len(storm_predictions) > 2 else 'Moderate' if len(storm_predictions) > 0 else 'Routine'}
1530
+
1531
+ 🔬 RESEARCH APPLICATIONS:
1532
+ • Suitable for seasonal planning and climate studies
1533
+ • Genesis mechanism investigation
1534
+ • ENSO-typhoon relationship analysis
1535
+ • Environmental parameter sensitivity studies
1536
+
1537
+ ⚠️ IMPORTANT DISCLAIMERS:
1538
+ • This is a research prediction system, not operational forecast
1539
+ • Use official meteorological services for real-time warnings
1540
+ • Actual conditions may differ from climatological assumptions
1541
+ • Model simplified compared to operational prediction systems
1542
+ • Uncertainty grows significantly beyond 5-7 day lead times
1543
+ """
1544
+
1545
+ return summary_text
1546
+
1547
+ except Exception as e:
1548
+ logging.error(f"Error creating prediction summary: {e}")
1549
+ return f"Error generating summary: {str(e)}"
1550
+
1551
  # -----------------------------
1552
  # FIXED: ADVANCED ML FEATURES WITH ROBUST ERROR HANDLING
1553
  # -----------------------------
 
2255
  )
2256
  return error_fig, error_fig, error_fig, error_fig, f"Error in clustering: {str(e)}"
2257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2258
  # -----------------------------
2259
  # Regression Functions (Original)
2260
  # -----------------------------
 
2832
  """
2833
  gr.Markdown(overview_text)
2834
 
2835
+ with gr.Tab("🌊 Monthly Typhoon Genesis Prediction"):
2836
+ gr.Markdown("## 🌊 Monthly Typhoon Genesis Prediction")
2837
+ gr.Markdown("**Enter month (1-12) and ONI value to see realistic typhoon development throughout the month using Genesis Potential Index**")
2838
 
2839
  with gr.Row():
2840
  with gr.Column(scale=1):
2841
+ genesis_month = gr.Slider(
2842
  1, 12,
2843
+ label="Month",
2844
  value=9,
2845
+ step=1,
2846
+ info="1=Jan, 2=Feb, ..., 12=Dec"
2847
  )
2848
+ genesis_oni = gr.Number(
2849
  label="ONI Value",
2850
  value=0.0,
2851
  info="El Niño (+) / La Niña (-) / Neutral (0)"
2852
  )
2853
+ enable_genesis_animation = gr.Checkbox(
2854
  label="Enable Animation",
2855
  value=True,
2856
+ info="Watch daily genesis potential evolution"
2857
  )
2858
+ generate_genesis_btn = gr.Button("🌊 Generate Monthly Genesis Prediction", variant="primary", size="lg")
2859
 
2860
  with gr.Column(scale=2):
2861
  gr.Markdown("### 🌊 What You'll Get:")
2862
  gr.Markdown("""
2863
+ - **Daily GPI Evolution**: See genesis potential change day-by-day throughout the month
2864
+ - **Genesis Event Detection**: Automatic identification of likely cyclogenesis times/locations
2865
+ - **Storm Track Development**: Physics-based tracks from each genesis point
2866
+ - **Real-time Animation**: Watch storms develop and move with uncertainty visualization
2867
+ - **Environmental Analysis**: SST, humidity, wind shear, and vorticity effects
2868
+ - **ENSO Modulation**: How El Niño/La Niña affects monthly patterns
2869
  """)
2870
 
2871
  with gr.Row():
2872
+ genesis_animation = gr.Plot(label="🗺️ Daily Genesis Potential & Storm Development")
 
 
 
2873
 
2874
  with gr.Row():
2875
+ genesis_summary = gr.Textbox(label="📋 Monthly Genesis Analysis Summary", lines=25)
2876
 
2877
+ def run_genesis_prediction(month, oni, animation):
2878
  try:
2879
+ # Generate monthly prediction using GPI
2880
+ prediction_data = generate_genesis_prediction_monthly(month, oni, year=2025)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2881
 
2882
+ # Create animation
2883
+ genesis_fig = create_genesis_animation(prediction_data, animation)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2884
 
2885
+ # Generate summary
2886
+ summary_text = create_prediction_summary(prediction_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2887
 
2888
+ return genesis_fig, summary_text
2889
 
2890
  except Exception as e:
2891
  import traceback
2892
+ error_msg = f"Genesis prediction failed: {str(e)}\n\nDetails:\n{traceback.format_exc()}"
2893
  logging.error(error_msg)
2894
+ return create_error_plot(error_msg), error_msg
2895
 
2896
+ generate_genesis_btn.click(
2897
+ fn=run_genesis_prediction,
2898
+ inputs=[genesis_month, genesis_oni, enable_genesis_animation],
2899
+ outputs=[genesis_animation, genesis_summary]
2900
  )
2901
 
2902
  with gr.Tab("🔬 Advanced ML Clustering"):