euler314 commited on
Commit
08112ee
Β·
verified Β·
1 Parent(s): cb646ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +449 -183
app.py CHANGED
@@ -102,11 +102,14 @@ ONI_DATA_PATH = os.path.join(DATA_PATH, 'oni_data.csv')
102
  TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv')
103
  MERGED_DATA_CSV = os.path.join(DATA_PATH, 'merged_typhoon_era5_data.csv')
104
 
105
- # IBTrACS settings
106
  BASIN_FILES = {
107
  'EP': 'ibtracs.EP.list.v04r01.csv',
108
  'NA': 'ibtracs.NA.list.v04r01.csv',
109
- 'WP': 'ibtracs.WP.list.v04r01.csv'
 
 
 
110
  }
111
  IBTRACS_BASE_URL = 'https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/csv/'
112
  LOCAL_IBTRACS_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv')
@@ -283,7 +286,7 @@ def get_fallback_data_dir():
283
  return os.getcwd()
284
 
285
  # -----------------------------
286
- # ONI and Typhoon Data Functions
287
  # -----------------------------
288
 
289
  def download_oni_file(url, filename):
@@ -335,7 +338,7 @@ def convert_oni_ascii_to_csv(input_file, output_file):
335
  return False
336
 
337
  def update_oni_data():
338
- """Update ONI data with error handling"""
339
  url = "https://www.cpc.ncep.noaa.gov/data/indices/oni.ascii.txt"
340
  temp_file = os.path.join(DATA_PATH, "temp_oni.ascii.txt")
341
  input_file = os.path.join(DATA_PATH, "oni.ascii.txt")
@@ -357,8 +360,8 @@ def update_oni_data():
357
  create_fallback_oni_data(output_file)
358
 
359
  def create_fallback_oni_data(output_file):
360
- """Create minimal ONI data for testing"""
361
- years = range(1950, 2026) # Extended to include 2025
362
  months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
363
 
364
  # Create synthetic ONI data
@@ -375,7 +378,7 @@ def create_fallback_oni_data(output_file):
375
  safe_file_write(output_file, df, get_fallback_data_dir())
376
 
377
  # -----------------------------
378
- # FIXED: IBTrACS Data Loading
379
  # -----------------------------
380
 
381
  def download_ibtracs_file(basin, force_download=False):
@@ -472,7 +475,7 @@ def load_ibtracs_csv_directly(basin='WP'):
472
  if col in df.columns:
473
  df[col] = pd.to_numeric(df[col], errors='coerce')
474
 
475
- # Filter out invalid/missing critical data
476
  valid_rows = df['LAT'].notna() & df['LON'].notna()
477
  df = df[valid_rows]
478
 
@@ -499,13 +502,13 @@ def load_ibtracs_csv_directly(basin='WP'):
499
  return None
500
 
501
  def load_ibtracs_data_fixed():
502
- """Fixed version of IBTrACS data loading"""
503
  ibtracs_data = {}
504
 
505
- # Try to load each basin, but prioritize WP for this application
506
- load_order = ['WP', 'EP', 'NA']
507
 
508
- for basin in load_order:
509
  try:
510
  logging.info(f"Loading {basin} basin data...")
511
  df = load_ibtracs_csv_directly(basin)
@@ -524,28 +527,26 @@ def load_ibtracs_data_fixed():
524
  return ibtracs_data
525
 
526
  def load_data_fixed(oni_path, typhoon_path):
527
- """Fixed version of load_data function"""
528
- # Load ONI data
529
  oni_data = pd.DataFrame({'Year': [], 'Jan': [], 'Feb': [], 'Mar': [], 'Apr': [],
530
  'May': [], 'Jun': [], 'Jul': [], 'Aug': [], 'Sep': [],
531
  'Oct': [], 'Nov': [], 'Dec': []})
532
 
533
- if not os.path.exists(oni_path):
534
- logging.warning(f"ONI data file not found: {oni_path}")
535
- update_oni_data()
536
-
537
- try:
538
- oni_data = pd.read_csv(oni_path)
539
- logging.info(f"Successfully loaded ONI data with {len(oni_data)} years")
540
- except Exception as e:
541
- logging.error(f"Error loading ONI data: {e}")
542
- update_oni_data()
543
  try:
544
  oni_data = pd.read_csv(oni_path)
 
 
545
  except Exception as e:
546
- logging.error(f"Still can't load ONI data: {e}")
 
 
 
 
547
 
548
- # Load typhoon data - NEW APPROACH
549
  typhoon_data = None
550
 
551
  # First, try to load from existing processed file
@@ -565,18 +566,19 @@ def load_data_fixed(oni_path, typhoon_path):
565
  logging.error(f"Error loading processed typhoon data: {e}")
566
  typhoon_data = None
567
 
568
- # If no valid processed data, load from IBTrACS
569
  if typhoon_data is None or typhoon_data.empty:
570
- logging.info("Loading typhoon data from IBTrACS...")
571
  ibtracs_data = load_ibtracs_data_fixed()
572
 
573
- # Combine all available basin data, prioritizing WP
574
  combined_dfs = []
575
- for basin in ['WP', 'EP', 'NA']:
576
  if basin in ibtracs_data and ibtracs_data[basin] is not None:
577
  df = ibtracs_data[basin].copy()
578
  df['BASIN'] = basin
579
  combined_dfs.append(df)
 
580
 
581
  if combined_dfs:
582
  typhoon_data = pd.concat(combined_dfs, ignore_index=True)
@@ -594,11 +596,11 @@ def load_data_fixed(oni_path, typhoon_path):
594
 
595
  # Save the processed data for future use
596
  safe_file_write(typhoon_path, typhoon_data, get_fallback_data_dir())
597
- logging.info(f"Combined IBTrACS data: {len(typhoon_data)} total records")
598
  else:
599
  logging.error("Failed to load any IBTrACS basin data")
600
- # Create minimal fallback data
601
- typhoon_data = create_fallback_typhoon_data()
602
 
603
  # Final validation of typhoon data
604
  if typhoon_data is not None:
@@ -628,70 +630,133 @@ def load_data_fixed(oni_path, typhoon_path):
628
  typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce')
629
 
630
  # Remove rows with invalid coordinates
631
- typhoon_data = typhoon_data.dropna(subset=['LAT', 'LON'])
 
632
 
633
  logging.info(f"Final typhoon data: {len(typhoon_data)} records after validation")
 
 
 
 
 
634
 
635
  return oni_data, typhoon_data
636
 
637
- def create_fallback_typhoon_data():
638
- """Create minimal fallback typhoon data - FIXED VERSION"""
639
- # Use proper pandas date_range instead of numpy
640
- dates = pd.date_range(start='1950-01-01', end='2025-12-31', freq='D') # Extended to 2025
641
- storm_dates = dates[np.random.choice(len(dates), size=100, replace=False)]
 
 
 
 
 
 
 
 
 
642
 
643
  data = []
644
- for i, date in enumerate(storm_dates):
645
- # Create realistic WP storm tracks
646
- base_lat = np.random.uniform(10, 30)
647
- base_lon = np.random.uniform(130, 160)
648
-
649
- # Generate 20-50 data points per storm
650
- track_length = np.random.randint(20, 51)
651
- sid = f"WP{i+1:02d}{date.year}"
652
-
653
- for j in range(track_length):
654
- lat = base_lat + j * 0.2 + np.random.normal(0, 0.1)
655
- lon = base_lon + j * 0.3 + np.random.normal(0, 0.1)
656
- wind = max(25, 70 + np.random.normal(0, 20))
657
- pres = max(950, 1000 - wind + np.random.normal(0, 5))
658
-
659
- data.append({
660
- 'SID': sid,
661
- 'ISO_TIME': date + pd.Timedelta(hours=j*6), # Use pd.Timedelta instead
662
- 'NAME': f'FALLBACK_{i+1}',
663
- 'SEASON': date.year,
664
- 'LAT': lat,
665
- 'LON': lon,
666
- 'USA_WIND': wind,
667
- 'USA_PRES': pres,
668
- 'BASIN': 'WP'
669
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
670
 
671
  df = pd.DataFrame(data)
672
- logging.info(f"Created fallback typhoon data with {len(df)} records")
673
  return df
674
 
675
  def process_oni_data(oni_data):
676
- """Process ONI data into long format"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  oni_long = oni_data.melt(id_vars=['Year'], var_name='Month', value_name='ONI')
678
  month_map = {'Jan':'01','Feb':'02','Mar':'03','Apr':'04','May':'05','Jun':'06',
679
  'Jul':'07','Aug':'08','Sep':'09','Oct':'10','Nov':'11','Dec':'12'}
680
  oni_long['Month'] = oni_long['Month'].map(month_map)
681
  oni_long['Date'] = pd.to_datetime(oni_long['Year'].astype(str)+'-'+oni_long['Month']+'-01')
682
- oni_long['ONI'] = pd.to_numeric(oni_long['ONI'], errors='coerce')
683
  return oni_long
684
 
685
  def process_typhoon_data(typhoon_data):
686
- """Process typhoon data"""
 
 
 
 
687
  if 'ISO_TIME' in typhoon_data.columns:
688
  typhoon_data['ISO_TIME'] = pd.to_datetime(typhoon_data['ISO_TIME'], errors='coerce')
689
  typhoon_data['USA_WIND'] = pd.to_numeric(typhoon_data['USA_WIND'], errors='coerce')
690
  typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce')
691
  typhoon_data['LON'] = pd.to_numeric(typhoon_data['LON'], errors='coerce')
692
 
693
- logging.info(f"Unique basins in typhoon_data: {typhoon_data['SID'].str[:2].unique()}")
 
 
 
694
 
 
695
  typhoon_max = typhoon_data.groupby('SID').agg({
696
  'USA_WIND':'max','USA_PRES':'min','ISO_TIME':'first','SEASON':'first','NAME':'first',
697
  'LAT':'first','LON':'first'
@@ -705,12 +770,35 @@ def process_typhoon_data(typhoon_data):
705
  typhoon_max['Month'] = '01'
706
  typhoon_max['Year'] = typhoon_max['SEASON']
707
 
 
708
  typhoon_max['Category'] = typhoon_max['USA_WIND'].apply(categorize_typhoon_enhanced)
 
 
709
  return typhoon_max
710
 
711
  def merge_data(oni_long, typhoon_max):
712
- """Merge ONI and typhoon data"""
713
- return pd.merge(typhoon_max, oni_long, on=['Year','Month'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
 
715
  # -----------------------------
716
  # ENHANCED: Categorization Functions - FIXED TAIWAN CLASSIFICATION
@@ -775,11 +863,11 @@ def categorize_typhoon(wind_speed):
775
  return categorize_typhoon_enhanced(wind_speed)
776
 
777
  def classify_enso_phases(oni_value):
778
- """Classify ENSO phases based on ONI value"""
779
  if isinstance(oni_value, pd.Series):
780
  oni_value = oni_value.iloc[0]
781
  if pd.isna(oni_value):
782
- return 'Neutral'
783
  if oni_value >= 0.5:
784
  return 'El Nino'
785
  elif oni_value <= -0.5:
@@ -2395,9 +2483,16 @@ MODEL: {prediction_results['model_info']}
2395
 
2396
  def perform_wind_regression(start_year, start_month, end_year, end_month):
2397
  """Perform wind regression analysis"""
 
 
 
2398
  start_date = datetime(start_year, start_month, 1)
2399
  end_date = datetime(end_year, end_month, 28)
2400
  data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].dropna(subset=['USA_WIND','ONI'])
 
 
 
 
2401
  data['severe_typhoon'] = (data['USA_WIND']>=64).astype(int)
2402
  X = sm.add_constant(data['ONI'])
2403
  y = data['severe_typhoon']
@@ -2412,9 +2507,16 @@ def perform_wind_regression(start_year, start_month, end_year, end_month):
2412
 
2413
  def perform_pressure_regression(start_year, start_month, end_year, end_month):
2414
  """Perform pressure regression analysis"""
 
 
 
2415
  start_date = datetime(start_year, start_month, 1)
2416
  end_date = datetime(end_year, end_month, 28)
2417
  data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].dropna(subset=['USA_PRES','ONI'])
 
 
 
 
2418
  data['intense_typhoon'] = (data['USA_PRES']<=950).astype(int)
2419
  X = sm.add_constant(data['ONI'])
2420
  y = data['intense_typhoon']
@@ -2429,9 +2531,16 @@ def perform_pressure_regression(start_year, start_month, end_year, end_month):
2429
 
2430
  def perform_longitude_regression(start_year, start_month, end_year, end_month):
2431
  """Perform longitude regression analysis"""
 
 
 
2432
  start_date = datetime(start_year, start_month, 1)
2433
  end_date = datetime(end_year, end_month, 28)
2434
  data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].dropna(subset=['LON','ONI'])
 
 
 
 
2435
  data['western_typhoon'] = (data['LON']<=140).astype(int)
2436
  X = sm.add_constant(data['ONI'])
2437
  y = data['western_typhoon']
@@ -2445,33 +2554,88 @@ def perform_longitude_regression(start_year, start_month, end_year, end_month):
2445
  return f"Longitude Regression Error: {e}"
2446
 
2447
  # -----------------------------
2448
- # Visualization Functions (Enhanced)
2449
  # -----------------------------
2450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2451
  def get_full_tracks(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2452
- """Get full typhoon tracks"""
2453
  start_date = datetime(start_year, start_month, 1)
2454
  end_date = datetime(end_year, end_month, 28)
2455
- filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2456
- filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2457
- if enso_phase != 'all':
2458
- filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
2459
- unique_storms = filtered_data['SID'].unique()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2460
  count = len(unique_storms)
2461
  fig = go.Figure()
 
2462
  for sid in unique_storms:
2463
  storm_data = typhoon_data[typhoon_data['SID']==sid]
2464
  if storm_data.empty:
2465
  continue
 
2466
  name = storm_data['NAME'].iloc[0] if pd.notnull(storm_data['NAME'].iloc[0]) else "Unnamed"
2467
- basin = storm_data['SID'].iloc[0][:2]
2468
- storm_oni = filtered_data[filtered_data['SID']==sid]['ONI'].iloc[0]
 
 
 
 
 
 
2469
  color = 'red' if storm_oni>=0.5 else ('blue' if storm_oni<=-0.5 else 'green')
 
2470
  fig.add_trace(go.Scattergeo(
2471
  lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines',
2472
  name=f"{name} ({basin})",
2473
  line=dict(width=1.5, color=color), hoverinfo="name"
2474
  ))
 
 
2475
  if typhoon_search:
2476
  search_mask = typhoon_data['NAME'].str.contains(typhoon_search, case=False, na=False)
2477
  if search_mask.any():
@@ -2483,8 +2647,9 @@ def get_full_tracks(start_year, start_month, end_year, end_month, enso_phase, ty
2483
  line=dict(width=3, color='yellow'),
2484
  marker=dict(size=5), hoverinfo="name"
2485
  ))
 
2486
  fig.update_layout(
2487
- title=f"Typhoon Tracks ({start_year}-{start_month} to {end_year}-{end_month})",
2488
  geo=dict(
2489
  projection_type='natural earth',
2490
  showland=True,
@@ -2499,26 +2664,48 @@ def get_full_tracks(start_year, start_month, end_year, end_month, enso_phase, ty
2499
  showlegend=True,
2500
  height=700
2501
  )
 
2502
  fig.add_annotation(
2503
  x=0.02, y=0.98, xref="paper", yref="paper",
2504
- text="Red: El NiΓ±o, Blue: La Nina, Green: Neutral",
2505
  showarrow=False, align="left",
2506
  bgcolor="rgba(255,255,255,0.8)"
2507
  )
2508
- return fig, f"Total typhoons displayed: {count}"
 
2509
 
2510
  def get_wind_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2511
- """Get wind analysis with enhanced categorization"""
2512
  start_date = datetime(start_year, start_month, 1)
2513
  end_date = datetime(end_year, end_month, 28)
2514
- filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2515
- filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2516
- if enso_phase != 'all':
2517
- filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2518
 
2519
  fig = px.scatter(filtered_data, x='ONI', y='USA_WIND', color='Category',
2520
  hover_data=['NAME','Year','Category'],
2521
- title='Wind Speed vs ONI',
2522
  labels={'ONI':'ONI Value','USA_WIND':'Max Wind Speed (knots)'},
2523
  color_discrete_map=enhanced_color_map)
2524
 
@@ -2532,21 +2719,49 @@ def get_wind_analysis(start_year, start_month, end_year, end_month, enso_phase,
2532
  text=filtered_data.loc[mask,'NAME']+' ('+filtered_data.loc[mask,'Year'].astype(str)+')'
2533
  ))
2534
 
2535
- regression = perform_wind_regression(start_year, start_month, end_year, end_month)
 
 
 
 
 
 
 
 
2536
  return fig, regression
2537
 
2538
  def get_pressure_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2539
- """Get pressure analysis with enhanced categorization"""
2540
  start_date = datetime(start_year, start_month, 1)
2541
  end_date = datetime(end_year, end_month, 28)
2542
- filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2543
- filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2544
- if enso_phase != 'all':
2545
- filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2546
 
2547
  fig = px.scatter(filtered_data, x='ONI', y='USA_PRES', color='Category',
2548
  hover_data=['NAME','Year','Category'],
2549
- title='Pressure vs ONI',
2550
  labels={'ONI':'ONI Value','USA_PRES':'Min Pressure (hPa)'},
2551
  color_discrete_map=enhanced_color_map)
2552
 
@@ -2560,68 +2775,73 @@ def get_pressure_analysis(start_year, start_month, end_year, end_month, enso_pha
2560
  text=filtered_data.loc[mask,'NAME']+' ('+filtered_data.loc[mask,'Year'].astype(str)+')'
2561
  ))
2562
 
2563
- regression = perform_pressure_regression(start_year, start_month, end_year, end_month)
 
 
 
 
 
 
 
 
2564
  return fig, regression
2565
 
2566
  def get_longitude_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2567
- """Get longitude analysis"""
2568
  start_date = datetime(start_year, start_month, 1)
2569
  end_date = datetime(end_year, end_month, 28)
2570
- filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2571
- filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2572
- if enso_phase != 'all':
2573
- filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2574
 
2575
  fig = px.scatter(filtered_data, x='LON', y='ONI', hover_data=['NAME'],
2576
- title='Typhoon Generation Longitude vs ONI (All Years)')
2577
 
2578
- if len(filtered_data) > 1:
2579
- X = np.array(filtered_data['LON']).reshape(-1,1)
2580
- y = filtered_data['ONI']
 
2581
  try:
 
 
2582
  model = sm.OLS(y, sm.add_constant(X)).fit()
2583
  y_pred = model.predict(sm.add_constant(X))
2584
  fig.add_trace(go.Scatter(x=filtered_data['LON'], y=y_pred, mode='lines', name='Regression Line'))
2585
  slope = model.params[1]
2586
- slopes_text = f"All Years Slope: {slope:.4f}"
2587
  except Exception as e:
2588
  slopes_text = f"Regression Error: {e}"
2589
- else:
2590
- slopes_text = "Insufficient data for regression"
 
 
 
2591
 
2592
- regression = perform_longitude_regression(start_year, start_month, end_year, end_month)
2593
  return fig, slopes_text, regression
2594
 
2595
  # -----------------------------
2596
  # ENHANCED: Animation Functions with Taiwan Standard Support - FIXED VERSION
2597
  # -----------------------------
2598
 
2599
- def get_available_years(typhoon_data):
2600
- """Get all available years including 2025 - with error handling"""
2601
- try:
2602
- if typhoon_data is None or typhoon_data.empty:
2603
- return [str(year) for year in range(2000, 2026)]
2604
-
2605
- if 'ISO_TIME' in typhoon_data.columns:
2606
- years = typhoon_data['ISO_TIME'].dt.year.dropna().unique()
2607
- elif 'SEASON' in typhoon_data.columns:
2608
- years = typhoon_data['SEASON'].dropna().unique()
2609
- else:
2610
- years = range(2000, 2026) # Default range including 2025
2611
-
2612
- # Convert to strings and sort
2613
- year_strings = sorted([str(int(year)) for year in years if not pd.isna(year)])
2614
-
2615
- # Ensure we have at least some years
2616
- if not year_strings:
2617
- return [str(year) for year in range(1950, 2026)]
2618
-
2619
- return year_strings
2620
-
2621
- except Exception as e:
2622
- print(f"Error in get_available_years: {e}")
2623
- return [str(year) for year in range(1950, 2026)]
2624
-
2625
  def update_typhoon_options_enhanced(year, basin):
2626
  """Enhanced typhoon options with TD support and 2025 data"""
2627
  try:
@@ -2880,7 +3100,7 @@ def simplified_track_video_fixed(year, basin, typhoon, standard):
2880
  return generate_enhanced_track_video_fixed(year, typhoon, standard)
2881
 
2882
  # -----------------------------
2883
- # Load & Process Data
2884
  # -----------------------------
2885
 
2886
  # Global variables initialization
@@ -2889,35 +3109,64 @@ typhoon_data = None
2889
  merged_data = None
2890
 
2891
  def initialize_data():
2892
- """Initialize all data safely"""
2893
  global oni_data, typhoon_data, merged_data
2894
  try:
2895
- logging.info("Starting data loading process...")
2896
- update_oni_data()
2897
- oni_data, typhoon_data = load_data_fixed(ONI_DATA_PATH, TYPHOON_DATA_PATH)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2898
 
2899
- if oni_data is not None and typhoon_data is not None:
2900
  oni_long = process_oni_data(oni_data)
2901
  typhoon_max = process_typhoon_data(typhoon_data)
2902
  merged_data = merge_data(oni_long, typhoon_max)
2903
- logging.info("Data loading complete.")
 
 
 
 
 
 
 
 
 
 
2904
  else:
2905
- logging.error("Failed to load required data")
2906
- # Create minimal fallback data
2907
- oni_data = pd.DataFrame({'Year': [2000], 'Jan': [0], 'Feb': [0], 'Mar': [0], 'Apr': [0],
2908
- 'May': [0], 'Jun': [0], 'Jul': [0], 'Aug': [0], 'Sep': [0],
2909
- 'Oct': [0], 'Nov': [0], 'Dec': [0]})
2910
- typhoon_data = create_fallback_typhoon_data()
2911
  oni_long = process_oni_data(oni_data)
2912
  typhoon_max = process_typhoon_data(typhoon_data)
2913
  merged_data = merge_data(oni_long, typhoon_max)
 
2914
  except Exception as e:
2915
  logging.error(f"Error during data initialization: {e}")
2916
- # Create minimal fallback data
2917
- oni_data = pd.DataFrame({'Year': [2000], 'Jan': [0], 'Feb': [0], 'Mar': [0], 'Apr': [0],
2918
- 'May': [0], 'Jun': [0], 'Jul': [0], 'Aug': [0], 'Sep': [0],
2919
- 'Oct': [0], 'Nov': [0], 'Dec': [0]})
2920
- typhoon_data = create_fallback_typhoon_data()
2921
  oni_long = process_oni_data(oni_data)
2922
  typhoon_max = process_typhoon_data(typhoon_data)
2923
  merged_data = merge_data(oni_long, typhoon_max)
@@ -2961,31 +3210,37 @@ def create_interface():
2961
  This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
2962
 
2963
  ### πŸš€ Enhanced Features:
 
 
 
2964
  - **Advanced ML Clustering**: UMAP/t-SNE storm pattern analysis with separate visualizations
2965
  - **Predictive Routing**: Advanced storm track and intensity forecasting with uncertainty quantification
2966
  - **Complete TD Support**: Now includes Tropical Depressions (< 34 kt)
2967
  - **Taiwan Standard**: Full support for Taiwan meteorological classification system
2968
- - **2025 Data Ready**: Real-time compatibility with current year data
2969
  - **Enhanced Animations**: High-quality storm track visualizations with both standards
2970
 
2971
  ### πŸ“Š Data Status:
2972
- - **ONI Data**: {len(oni_data)} years loaded
2973
  - **Typhoon Data**: {total_records:,} records loaded
2974
  - **Merged Data**: {len(merged_data):,} typhoons with ONI values
2975
  - **Available Years**: {year_range_display}
 
2976
 
2977
  ### πŸ”§ Technical Capabilities:
2978
  - **UMAP Clustering**: {"βœ… Available" if UMAP_AVAILABLE else "⚠️ Limited to t-SNE/PCA"}
2979
  - **AI Predictions**: {"🧠 Deep Learning" if CNN_AVAILABLE else "πŸ”¬ Physics-based"}
2980
  - **Enhanced Categorization**: Tropical Depression to Super Typhoon
2981
  - **Platform**: Optimized for Hugging Face Spaces
 
2982
 
2983
  ### πŸ“ˆ Research Applications:
2984
- - Climate change impact studies
2985
  - Seasonal forecasting research
2986
  - Storm pattern classification
2987
  - ENSO-typhoon relationship analysis
2988
  - Intensity prediction model development
 
 
2989
  """
2990
  gr.Markdown(overview_text)
2991
 
@@ -3044,6 +3299,8 @@ def create_interface():
3044
 
3045
  cluster_info_text = """
3046
  ### πŸ“Š Enhanced Clustering Features:
 
 
3047
  - **Separate Visualizations**: Four distinct plots for comprehensive analysis
3048
  - **Multi-dimensional Analysis**: Uses 15+ storm characteristics including intensity, track shape, genesis location
3049
  - **Route Visualization**: Geographic storm tracks colored by cluster membership
@@ -3115,9 +3372,9 @@ def create_interface():
3115
  label="Forecast Length (hours)",
3116
  value=72,
3117
  minimum=20,
3118
- maximum=100000,
3119
  step=6,
3120
- info="Extended forecasting: 20-1000hours (42 days max)"
3121
  )
3122
  advanced_physics = gr.Checkbox(
3123
  label="Advanced Physics",
@@ -3272,7 +3529,8 @@ def create_interface():
3272
  )
3273
  basin_dropdown = gr.Dropdown(
3274
  label="Basin",
3275
- choices=["All Basins", "WP - Western Pacific", "EP - Eastern Pacific", "NA - North Atlantic"],
 
3276
  value="All Basins"
3277
  )
3278
 
@@ -3306,9 +3564,10 @@ def create_interface():
3306
  # FIXED animation info text with corrected Taiwan standards
3307
  animation_info_text = """
3308
  ### 🎬 Enhanced Animation Features:
 
 
3309
  - **Dual Standards**: Full support for both Atlantic and Taiwan classification systems
3310
  - **Full TD Support**: Now displays Tropical Depressions (< 34 kt) in gray
3311
- - **2025 Compatibility**: Complete support for current year data
3312
  - **Enhanced Maps**: Better cartographic projections with terrain features
3313
  - **Smart Scaling**: Storm symbols scale dynamically with intensity
3314
  - **Real-time Info**: Live position, time, and meteorological data display
@@ -3346,7 +3605,7 @@ def create_interface():
3346
  fig_dist = px.bar(
3347
  x=cat_counts.index,
3348
  y=cat_counts.values,
3349
- title="Storm Intensity Distribution (Including Tropical Depressions)",
3350
  labels={'x': 'Category', 'y': 'Number of Storms'},
3351
  color=cat_counts.index,
3352
  color_discrete_map=enhanced_color_map
@@ -3361,7 +3620,7 @@ def create_interface():
3361
  fig_seasonal = px.bar(
3362
  x=monthly_counts.index,
3363
  y=monthly_counts.values,
3364
- title="Seasonal Storm Distribution",
3365
  labels={'x': 'Month', 'y': 'Number of Storms'},
3366
  color=monthly_counts.values,
3367
  color_continuous_scale='Viridis'
@@ -3370,12 +3629,12 @@ def create_interface():
3370
  fig_seasonal = None
3371
 
3372
  # Basin distribution
3373
- if 'SID' in typhoon_data.columns:
3374
- basin_data = typhoon_data['SID'].str[:2].value_counts()
3375
  fig_basin = px.pie(
3376
  values=basin_data.values,
3377
  names=basin_data.index,
3378
- title="Distribution by Basin"
3379
  )
3380
  else:
3381
  fig_basin = None
@@ -3394,7 +3653,7 @@ def create_interface():
3394
  except Exception as e:
3395
  gr.Markdown(f"Visualization error: {str(e)}")
3396
 
3397
- # Enhanced statistics - FIXED formatting
3398
  total_storms = len(typhoon_data['SID'].unique()) if 'SID' in typhoon_data.columns else 0
3399
  total_records = len(typhoon_data)
3400
 
@@ -3405,21 +3664,21 @@ def create_interface():
3405
  year_range = f"{min_year}-{max_year}"
3406
  years_covered = typhoon_data['SEASON'].nunique()
3407
  except (ValueError, TypeError):
3408
- year_range = "Unknown"
3409
- years_covered = 0
3410
  else:
3411
- year_range = "Unknown"
3412
- years_covered = 0
3413
 
3414
- if 'SID' in typhoon_data.columns:
3415
  try:
3416
- basins_available = ', '.join(sorted(typhoon_data['SID'].str[:2].unique()))
3417
  avg_storms_per_year = total_storms / max(years_covered, 1)
3418
  except Exception:
3419
- basins_available = "Unknown"
3420
  avg_storms_per_year = 0
3421
  else:
3422
- basins_available = "Unknown"
3423
  avg_storms_per_year = 0
3424
 
3425
  # TD specific statistics
@@ -3452,21 +3711,25 @@ def create_interface():
3452
  - **Typhoons (C1-C5)**: {typhoon_storms:,} storms
3453
 
3454
  ### πŸš€ Platform Capabilities:
 
 
 
3455
  - **Complete TD Analysis** - First platform to include comprehensive TD tracking
3456
  - **Dual Classification Systems** - Both Atlantic and Taiwan standards supported
3457
  - **Advanced ML Clustering** - DBSCAN pattern recognition with separate visualizations
3458
  - **Real-time Predictions** - Physics-based and optional CNN intensity forecasting
3459
- - **2025 Data Ready** - Full compatibility with current season data
3460
  - **Enhanced Animations** - Professional-quality storm track videos
3461
- - **Multi-basin Analysis** - Comprehensive Pacific and Atlantic coverage
3462
 
3463
  ### πŸ”¬ Research Applications:
3464
- - Climate change impact studies
3465
- - Seasonal forecasting research
3466
- - Storm pattern classification
3467
  - ENSO-typhoon relationship analysis
3468
  - Intensity prediction model development
3469
  - Cross-regional classification comparisons
 
 
3470
  """
3471
  gr.Markdown(stats_text)
3472
 
@@ -3525,6 +3788,9 @@ def create_minimal_fallback_interface():
3525
  File Checks:
3526
  - ONI Path Exists: {os.path.exists(ONI_DATA_PATH)}
3527
  - Typhoon Path Exists: {os.path.exists(TYPHOON_DATA_PATH)}
 
 
 
3528
  """
3529
  return debug_text
3530
 
 
102
  TYPHOON_DATA_PATH = os.path.join(DATA_PATH, 'processed_typhoon_data.csv')
103
  MERGED_DATA_CSV = os.path.join(DATA_PATH, 'merged_typhoon_era5_data.csv')
104
 
105
+ # IBTrACS settings - NOW INCLUDES ALL BASINS
106
  BASIN_FILES = {
107
  'EP': 'ibtracs.EP.list.v04r01.csv',
108
  'NA': 'ibtracs.NA.list.v04r01.csv',
109
+ 'WP': 'ibtracs.WP.list.v04r01.csv',
110
+ 'SP': 'ibtracs.SP.list.v04r01.csv', # Added South Pacific
111
+ 'SI': 'ibtracs.SI.list.v04r01.csv', # Added South Indian
112
+ 'NI': 'ibtracs.NI.list.v04r01.csv' # Added North Indian
113
  }
114
  IBTRACS_BASE_URL = 'https://www.ncei.noaa.gov/data/international-best-track-archive-for-climate-stewardship-ibtracs/v04r01/access/csv/'
115
  LOCAL_IBTRACS_PATH = os.path.join(DATA_PATH, 'ibtracs.WP.list.v04r01.csv')
 
286
  return os.getcwd()
287
 
288
  # -----------------------------
289
+ # ONI and Typhoon Data Functions - FIXED TO LOAD ALL DATA
290
  # -----------------------------
291
 
292
  def download_oni_file(url, filename):
 
338
  return False
339
 
340
  def update_oni_data():
341
+ """Update ONI data with error handling - OPTIONAL now"""
342
  url = "https://www.cpc.ncep.noaa.gov/data/indices/oni.ascii.txt"
343
  temp_file = os.path.join(DATA_PATH, "temp_oni.ascii.txt")
344
  input_file = os.path.join(DATA_PATH, "oni.ascii.txt")
 
360
  create_fallback_oni_data(output_file)
361
 
362
  def create_fallback_oni_data(output_file):
363
+ """Create minimal ONI data for testing - EXTENDED RANGE"""
364
+ years = range(1851, 2026) # Extended to full historical range
365
  months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
366
 
367
  # Create synthetic ONI data
 
378
  safe_file_write(output_file, df, get_fallback_data_dir())
379
 
380
  # -----------------------------
381
+ # FIXED: IBTrACS Data Loading - LOAD ALL BASINS, ALL YEARS
382
  # -----------------------------
383
 
384
  def download_ibtracs_file(basin, force_download=False):
 
475
  if col in df.columns:
476
  df[col] = pd.to_numeric(df[col], errors='coerce')
477
 
478
+ # Filter out invalid/missing critical data - BUT KEEP ALL YEARS
479
  valid_rows = df['LAT'].notna() & df['LON'].notna()
480
  df = df[valid_rows]
481
 
 
502
  return None
503
 
504
  def load_ibtracs_data_fixed():
505
+ """FIXED: Load ALL AVAILABLE BASIN DATA without restrictions"""
506
  ibtracs_data = {}
507
 
508
+ # Load ALL basins available
509
+ all_basins = ['WP', 'EP', 'NA', 'SP', 'SI', 'NI'] # All available basins
510
 
511
+ for basin in all_basins:
512
  try:
513
  logging.info(f"Loading {basin} basin data...")
514
  df = load_ibtracs_csv_directly(basin)
 
527
  return ibtracs_data
528
 
529
  def load_data_fixed(oni_path, typhoon_path):
530
+ """FIXED: Load ALL typhoon data regardless of ONI availability"""
531
+ # Load ONI data - OPTIONAL now
532
  oni_data = pd.DataFrame({'Year': [], 'Jan': [], 'Feb': [], 'Mar': [], 'Apr': [],
533
  'May': [], 'Jun': [], 'Jul': [], 'Aug': [], 'Sep': [],
534
  'Oct': [], 'Nov': [], 'Dec': []})
535
 
536
+ oni_available = False
537
+ if os.path.exists(oni_path):
 
 
 
 
 
 
 
 
538
  try:
539
  oni_data = pd.read_csv(oni_path)
540
+ logging.info(f"Successfully loaded ONI data with {len(oni_data)} years")
541
+ oni_available = True
542
  except Exception as e:
543
+ logging.error(f"Error loading ONI data: {e}")
544
+ oni_available = False
545
+ else:
546
+ logging.warning(f"ONI data file not found: {oni_path} - proceeding without ONI")
547
+ oni_available = False
548
 
549
+ # Load typhoon data - ALWAYS LOAD ALL AVAILABLE DATA
550
  typhoon_data = None
551
 
552
  # First, try to load from existing processed file
 
566
  logging.error(f"Error loading processed typhoon data: {e}")
567
  typhoon_data = None
568
 
569
+ # If no valid processed data, load from IBTrACS - LOAD ALL BASINS
570
  if typhoon_data is None or typhoon_data.empty:
571
+ logging.info("Loading ALL available typhoon data from IBTrACS...")
572
  ibtracs_data = load_ibtracs_data_fixed()
573
 
574
+ # Combine ALL available basin data
575
  combined_dfs = []
576
+ for basin in ['WP', 'EP', 'NA', 'SP', 'SI', 'NI']:
577
  if basin in ibtracs_data and ibtracs_data[basin] is not None:
578
  df = ibtracs_data[basin].copy()
579
  df['BASIN'] = basin
580
  combined_dfs.append(df)
581
+ logging.info(f"Added {len(df)} records from {basin} basin")
582
 
583
  if combined_dfs:
584
  typhoon_data = pd.concat(combined_dfs, ignore_index=True)
 
596
 
597
  # Save the processed data for future use
598
  safe_file_write(typhoon_path, typhoon_data, get_fallback_data_dir())
599
+ logging.info(f"Combined IBTrACS data: {len(typhoon_data)} total records from all basins")
600
  else:
601
  logging.error("Failed to load any IBTrACS basin data")
602
+ # Create comprehensive fallback data
603
+ typhoon_data = create_comprehensive_fallback_typhoon_data()
604
 
605
  # Final validation of typhoon data
606
  if typhoon_data is not None:
 
630
  typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce')
631
 
632
  # Remove rows with invalid coordinates
633
+ valid_coords = typhoon_data['LAT'].notna() & typhoon_data['LON'].notna()
634
+ typhoon_data = typhoon_data[valid_coords]
635
 
636
  logging.info(f"Final typhoon data: {len(typhoon_data)} records after validation")
637
+
638
+ # Log basin distribution
639
+ if 'BASIN' in typhoon_data.columns:
640
+ basin_counts = typhoon_data['BASIN'].value_counts()
641
+ logging.info(f"Basin distribution: {dict(basin_counts)}")
642
 
643
  return oni_data, typhoon_data
644
 
645
+ def create_comprehensive_fallback_typhoon_data():
646
+ """Create comprehensive fallback typhoon data - ALL BASINS, ALL YEARS"""
647
+ # Extended date range and multiple basins
648
+ dates = pd.date_range(start='1851-01-01', end='2025-12-31', freq='D')
649
+
650
+ # Define basin parameters
651
+ basin_configs = {
652
+ 'WP': {'lat_range': (5, 45), 'lon_range': (100, 180), 'count': 200},
653
+ 'EP': {'lat_range': (5, 35), 'lon_range': (-180, -80), 'count': 150},
654
+ 'NA': {'lat_range': (5, 45), 'lon_range': (-100, -10), 'count': 100},
655
+ 'SP': {'lat_range': (-40, -5), 'lon_range': (135, 240), 'count': 80},
656
+ 'SI': {'lat_range': (-40, -5), 'lon_range': (30, 135), 'count': 70},
657
+ 'NI': {'lat_range': (5, 30), 'lon_range': (40, 100), 'count': 50}
658
+ }
659
 
660
  data = []
661
+
662
+ for basin, config in basin_configs.items():
663
+ # Generate storms for this basin
664
+ storm_dates = dates[np.random.choice(len(dates), size=config['count'], replace=False)]
665
+
666
+ for i, date in enumerate(storm_dates):
667
+ # Create realistic storm tracks for this basin
668
+ lat_min, lat_max = config['lat_range']
669
+ lon_min, lon_max = config['lon_range']
670
+
671
+ base_lat = np.random.uniform(lat_min, lat_max)
672
+ base_lon = np.random.uniform(lon_min, lon_max)
673
+
674
+ # Generate 10-100 data points per storm (variable track lengths)
675
+ track_length = np.random.randint(10, 101)
676
+ sid = f"{basin}{i+1:02d}{date.year}"
677
+
678
+ for j in range(track_length):
679
+ # Realistic movement patterns
680
+ if basin in ['WP', 'EP', 'NA']: # Northern Hemisphere
681
+ lat = base_lat + j * 0.15 + np.random.normal(0, 0.1)
682
+ if basin == 'WP':
683
+ lon = base_lon + j * 0.2 + np.random.normal(0, 0.1)
684
+ else:
685
+ lon = base_lon - j * 0.2 + np.random.normal(0, 0.1)
686
+ else: # Southern Hemisphere
687
+ lat = base_lat - j * 0.15 + np.random.normal(0, 0.1)
688
+ lon = base_lon + j * 0.2 + np.random.normal(0, 0.1)
689
+
690
+ # Realistic intensity progression
691
+ if j < track_length * 0.3: # Development phase
692
+ wind = max(20, 25 + j * 3 + np.random.normal(0, 5))
693
+ elif j < track_length * 0.7: # Mature phase
694
+ wind = max(30, 60 + np.random.normal(0, 20))
695
+ else: # Decay phase
696
+ wind = max(20, 80 - (j - track_length * 0.7) * 2 + np.random.normal(0, 10))
697
+
698
+ pres = max(900, 1020 - wind + np.random.normal(0, 8))
699
+
700
+ data.append({
701
+ 'SID': sid,
702
+ 'ISO_TIME': date + pd.Timedelta(hours=j*6),
703
+ 'NAME': f'FALLBACK_{basin}_{i+1}',
704
+ 'SEASON': date.year,
705
+ 'LAT': lat,
706
+ 'LON': lon,
707
+ 'USA_WIND': wind,
708
+ 'USA_PRES': pres,
709
+ 'BASIN': basin
710
+ })
711
 
712
  df = pd.DataFrame(data)
713
+ logging.info(f"Created comprehensive fallback typhoon data with {len(df)} records across all basins")
714
  return df
715
 
716
  def process_oni_data(oni_data):
717
+ """Process ONI data into long format - HANDLE EMPTY DATA"""
718
+ if oni_data is None or oni_data.empty:
719
+ # Create minimal ONI data
720
+ logging.warning("No ONI data available, creating minimal dataset")
721
+ years = range(1950, 2026)
722
+ data = []
723
+ for year in years:
724
+ for month_num, month_name in enumerate(['Jan','Feb','Mar','Apr','May','Jun',
725
+ 'Jul','Aug','Sep','Oct','Nov','Dec'], 1):
726
+ data.append({
727
+ 'Year': year,
728
+ 'Month': f'{month_num:02d}',
729
+ 'ONI': 0.0,
730
+ 'Date': pd.to_datetime(f'{year}-{month_num:02d}-01')
731
+ })
732
+ return pd.DataFrame(data)
733
+
734
  oni_long = oni_data.melt(id_vars=['Year'], var_name='Month', value_name='ONI')
735
  month_map = {'Jan':'01','Feb':'02','Mar':'03','Apr':'04','May':'05','Jun':'06',
736
  'Jul':'07','Aug':'08','Sep':'09','Oct':'10','Nov':'11','Dec':'12'}
737
  oni_long['Month'] = oni_long['Month'].map(month_map)
738
  oni_long['Date'] = pd.to_datetime(oni_long['Year'].astype(str)+'-'+oni_long['Month']+'-01')
739
+ oni_long['ONI'] = pd.to_numeric(oni_long['ONI'], errors='coerce').fillna(0.0)
740
  return oni_long
741
 
742
  def process_typhoon_data(typhoon_data):
743
+ """Process typhoon data - ALWAYS PRESERVE ALL DATA"""
744
+ if typhoon_data is None or typhoon_data.empty:
745
+ return pd.DataFrame()
746
+
747
+ # Process without filtering based on ONI availability
748
  if 'ISO_TIME' in typhoon_data.columns:
749
  typhoon_data['ISO_TIME'] = pd.to_datetime(typhoon_data['ISO_TIME'], errors='coerce')
750
  typhoon_data['USA_WIND'] = pd.to_numeric(typhoon_data['USA_WIND'], errors='coerce')
751
  typhoon_data['USA_PRES'] = pd.to_numeric(typhoon_data['USA_PRES'], errors='coerce')
752
  typhoon_data['LON'] = pd.to_numeric(typhoon_data['LON'], errors='coerce')
753
 
754
+ # Log basin information
755
+ if 'SID' in typhoon_data.columns:
756
+ basins = typhoon_data['SID'].str[:2].unique()
757
+ logging.info(f"Available basins in typhoon data: {sorted(basins)}")
758
 
759
+ # Get maximum values per storm
760
  typhoon_max = typhoon_data.groupby('SID').agg({
761
  'USA_WIND':'max','USA_PRES':'min','ISO_TIME':'first','SEASON':'first','NAME':'first',
762
  'LAT':'first','LON':'first'
 
770
  typhoon_max['Month'] = '01'
771
  typhoon_max['Year'] = typhoon_max['SEASON']
772
 
773
+ # Categorize ALL storms (including very weak ones)
774
  typhoon_max['Category'] = typhoon_max['USA_WIND'].apply(categorize_typhoon_enhanced)
775
+
776
+ logging.info(f"Processed {len(typhoon_max)} unique storms")
777
  return typhoon_max
778
 
779
  def merge_data(oni_long, typhoon_max):
780
+ """FIXED: Merge data but KEEP ALL TYPHOON DATA even without ONI"""
781
+ if typhoon_max is None or typhoon_max.empty:
782
+ return pd.DataFrame()
783
+
784
+ if oni_long is None or oni_long.empty:
785
+ # No ONI data available - add dummy ONI values
786
+ logging.warning("No ONI data available - adding neutral ONI values")
787
+ typhoon_max['ONI'] = 0.0
788
+ return typhoon_max
789
+
790
+ # Use LEFT JOIN to keep all typhoon data
791
+ merged = pd.merge(typhoon_max, oni_long, on=['Year','Month'], how='left')
792
+
793
+ # Fill missing ONI values with neutral (0.0)
794
+ merged['ONI'] = merged['ONI'].fillna(0.0)
795
+
796
+ logging.info(f"Merged data: {len(merged)} storms total")
797
+ missing_oni = merged['ONI'].isna().sum()
798
+ if missing_oni > 0:
799
+ logging.info(f"Filled {missing_oni} missing ONI values with neutral (0.0)")
800
+
801
+ return merged
802
 
803
  # -----------------------------
804
  # ENHANCED: Categorization Functions - FIXED TAIWAN CLASSIFICATION
 
863
  return categorize_typhoon_enhanced(wind_speed)
864
 
865
  def classify_enso_phases(oni_value):
866
+ """Classify ENSO phases based on ONI value - HANDLE MISSING VALUES"""
867
  if isinstance(oni_value, pd.Series):
868
  oni_value = oni_value.iloc[0]
869
  if pd.isna(oni_value):
870
+ return 'Neutral' # Default to neutral for missing ONI
871
  if oni_value >= 0.5:
872
  return 'El Nino'
873
  elif oni_value <= -0.5:
 
2483
 
2484
  def perform_wind_regression(start_year, start_month, end_year, end_month):
2485
  """Perform wind regression analysis"""
2486
+ if merged_data is None or merged_data.empty:
2487
+ return "Wind Regression: No merged data available"
2488
+
2489
  start_date = datetime(start_year, start_month, 1)
2490
  end_date = datetime(end_year, end_month, 28)
2491
  data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].dropna(subset=['USA_WIND','ONI'])
2492
+
2493
+ if len(data) < 10:
2494
+ return f"Wind Regression: Insufficient data ({len(data)} records)"
2495
+
2496
  data['severe_typhoon'] = (data['USA_WIND']>=64).astype(int)
2497
  X = sm.add_constant(data['ONI'])
2498
  y = data['severe_typhoon']
 
2507
 
2508
  def perform_pressure_regression(start_year, start_month, end_year, end_month):
2509
  """Perform pressure regression analysis"""
2510
+ if merged_data is None or merged_data.empty:
2511
+ return "Pressure Regression: No merged data available"
2512
+
2513
  start_date = datetime(start_year, start_month, 1)
2514
  end_date = datetime(end_year, end_month, 28)
2515
  data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].dropna(subset=['USA_PRES','ONI'])
2516
+
2517
+ if len(data) < 10:
2518
+ return f"Pressure Regression: Insufficient data ({len(data)} records)"
2519
+
2520
  data['intense_typhoon'] = (data['USA_PRES']<=950).astype(int)
2521
  X = sm.add_constant(data['ONI'])
2522
  y = data['intense_typhoon']
 
2531
 
2532
  def perform_longitude_regression(start_year, start_month, end_year, end_month):
2533
  """Perform longitude regression analysis"""
2534
+ if merged_data is None or merged_data.empty:
2535
+ return "Longitude Regression: No merged data available"
2536
+
2537
  start_date = datetime(start_year, start_month, 1)
2538
  end_date = datetime(end_year, end_month, 28)
2539
  data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].dropna(subset=['LON','ONI'])
2540
+
2541
+ if len(data) < 10:
2542
+ return f"Longitude Regression: Insufficient data ({len(data)} records)"
2543
+
2544
  data['western_typhoon'] = (data['LON']<=140).astype(int)
2545
  X = sm.add_constant(data['ONI'])
2546
  y = data['western_typhoon']
 
2554
  return f"Longitude Regression Error: {e}"
2555
 
2556
  # -----------------------------
2557
+ # FIXED: Visualization Functions - WORK WITH ALL DATA
2558
  # -----------------------------
2559
 
2560
+ def get_available_years(typhoon_data):
2561
+ """Get all available years - EXTENDED RANGE"""
2562
+ try:
2563
+ if typhoon_data is None or typhoon_data.empty:
2564
+ return [str(year) for year in range(1851, 2026)] # Full historical range
2565
+
2566
+ if 'ISO_TIME' in typhoon_data.columns:
2567
+ years = typhoon_data['ISO_TIME'].dt.year.dropna().unique()
2568
+ elif 'SEASON' in typhoon_data.columns:
2569
+ years = typhoon_data['SEASON'].dropna().unique()
2570
+ else:
2571
+ years = range(1851, 2026) # Full historical range
2572
+
2573
+ # Convert to strings and sort
2574
+ year_strings = sorted([str(int(year)) for year in years if not pd.isna(year)])
2575
+
2576
+ # Ensure we have at least some years
2577
+ if not year_strings:
2578
+ return [str(year) for year in range(1851, 2026)]
2579
+
2580
+ return year_strings
2581
+
2582
+ except Exception as e:
2583
+ print(f"Error in get_available_years: {e}")
2584
+ return [str(year) for year in range(1851, 2026)]
2585
+
2586
  def get_full_tracks(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2587
+ """FIXED: Get full typhoon tracks - WORKS WITHOUT ONI"""
2588
  start_date = datetime(start_year, start_month, 1)
2589
  end_date = datetime(end_year, end_month, 28)
2590
+
2591
+ # Filter merged data by date
2592
+ if merged_data is not None and not merged_data.empty:
2593
+ filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2594
+
2595
+ # Add ENSO phase classification - handle missing ONI
2596
+ filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2597
+
2598
+ if enso_phase != 'all':
2599
+ filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
2600
+
2601
+ unique_storms = filtered_data['SID'].unique()
2602
+ else:
2603
+ # Work directly with typhoon_data if merged_data not available
2604
+ if 'ISO_TIME' in typhoon_data.columns:
2605
+ time_filter = (typhoon_data['ISO_TIME'] >= start_date) & (typhoon_data['ISO_TIME'] <= end_date)
2606
+ filtered_typhoons = typhoon_data[time_filter]['SID'].unique()
2607
+ else:
2608
+ # Fallback - use all available storms
2609
+ filtered_typhoons = typhoon_data['SID'].unique()
2610
+ unique_storms = filtered_typhoons
2611
+ filtered_data = pd.DataFrame({'SID': unique_storms, 'ONI': 0.0}) # Dummy for compatibility
2612
+
2613
  count = len(unique_storms)
2614
  fig = go.Figure()
2615
+
2616
  for sid in unique_storms:
2617
  storm_data = typhoon_data[typhoon_data['SID']==sid]
2618
  if storm_data.empty:
2619
  continue
2620
+
2621
  name = storm_data['NAME'].iloc[0] if pd.notnull(storm_data['NAME'].iloc[0]) else "Unnamed"
2622
+ basin = storm_data['SID'].iloc[0][:2] if 'SID' in storm_data.columns else "Unknown"
2623
+
2624
+ # Get ONI value if available
2625
+ if not filtered_data.empty and sid in filtered_data['SID'].values:
2626
+ storm_oni = filtered_data[filtered_data['SID']==sid]['ONI'].iloc[0]
2627
+ else:
2628
+ storm_oni = 0.0 # Default neutral
2629
+
2630
  color = 'red' if storm_oni>=0.5 else ('blue' if storm_oni<=-0.5 else 'green')
2631
+
2632
  fig.add_trace(go.Scattergeo(
2633
  lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines',
2634
  name=f"{name} ({basin})",
2635
  line=dict(width=1.5, color=color), hoverinfo="name"
2636
  ))
2637
+
2638
+ # Handle typhoon search
2639
  if typhoon_search:
2640
  search_mask = typhoon_data['NAME'].str.contains(typhoon_search, case=False, na=False)
2641
  if search_mask.any():
 
2647
  line=dict(width=3, color='yellow'),
2648
  marker=dict(size=5), hoverinfo="name"
2649
  ))
2650
+
2651
  fig.update_layout(
2652
+ title=f"Typhoon Tracks ({start_year}-{start_month:02d} to {end_year}-{end_month:02d}) - All Available Data",
2653
  geo=dict(
2654
  projection_type='natural earth',
2655
  showland=True,
 
2664
  showlegend=True,
2665
  height=700
2666
  )
2667
+
2668
  fig.add_annotation(
2669
  x=0.02, y=0.98, xref="paper", yref="paper",
2670
+ text="Red: El NiΓ±o, Blue: La Nina, Green: Neutral/Unknown ONI",
2671
  showarrow=False, align="left",
2672
  bgcolor="rgba(255,255,255,0.8)"
2673
  )
2674
+
2675
+ return fig, f"Total typhoons displayed: {count} (includes all available data)"
2676
 
2677
  def get_wind_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2678
+ """FIXED: Wind analysis that works with all data"""
2679
  start_date = datetime(start_year, start_month, 1)
2680
  end_date = datetime(end_year, end_month, 28)
2681
+
2682
+ if merged_data is not None and not merged_data.empty:
2683
+ filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2684
+ filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2685
+
2686
+ if enso_phase != 'all':
2687
+ filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
2688
+ else:
2689
+ # Create filtered data from typhoon_data
2690
+ if 'ISO_TIME' in typhoon_data.columns:
2691
+ time_filter = (typhoon_data['ISO_TIME'] >= start_date) & (typhoon_data['ISO_TIME'] <= end_date)
2692
+ temp_data = typhoon_data[time_filter].groupby('SID').agg({
2693
+ 'USA_WIND': 'max', 'NAME': 'first', 'SEASON': 'first', 'ISO_TIME': 'first'
2694
+ }).reset_index()
2695
+ temp_data['ONI'] = 0.0 # Default neutral
2696
+ temp_data['Category'] = temp_data['USA_WIND'].apply(categorize_typhoon_enhanced)
2697
+ temp_data['Year'] = temp_data['ISO_TIME'].dt.year
2698
+ temp_data['ENSO_Phase'] = 'Neutral'
2699
+ filtered_data = temp_data
2700
+ else:
2701
+ return go.Figure(), "No time data available for analysis"
2702
+
2703
+ if filtered_data.empty:
2704
+ return go.Figure(), "No data available for selected time period"
2705
 
2706
  fig = px.scatter(filtered_data, x='ONI', y='USA_WIND', color='Category',
2707
  hover_data=['NAME','Year','Category'],
2708
+ title='Wind Speed vs ONI (All Available Data)',
2709
  labels={'ONI':'ONI Value','USA_WIND':'Max Wind Speed (knots)'},
2710
  color_discrete_map=enhanced_color_map)
2711
 
 
2719
  text=filtered_data.loc[mask,'NAME']+' ('+filtered_data.loc[mask,'Year'].astype(str)+')'
2720
  ))
2721
 
2722
+ # Try regression analysis if we have sufficient data
2723
+ try:
2724
+ if len(filtered_data) > 10:
2725
+ regression = perform_wind_regression(start_year, start_month, end_year, end_month)
2726
+ else:
2727
+ regression = f"Wind Analysis: {len(filtered_data)} storms analyzed (insufficient for regression)"
2728
+ except:
2729
+ regression = f"Wind Analysis: {len(filtered_data)} storms analyzed"
2730
+
2731
  return fig, regression
2732
 
2733
  def get_pressure_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2734
+ """FIXED: Pressure analysis that works with all data"""
2735
  start_date = datetime(start_year, start_month, 1)
2736
  end_date = datetime(end_year, end_month, 28)
2737
+
2738
+ if merged_data is not None and not merged_data.empty:
2739
+ filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2740
+ filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2741
+
2742
+ if enso_phase != 'all':
2743
+ filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
2744
+ else:
2745
+ # Create filtered data from typhoon_data
2746
+ if 'ISO_TIME' in typhoon_data.columns:
2747
+ time_filter = (typhoon_data['ISO_TIME'] >= start_date) & (typhoon_data['ISO_TIME'] <= end_date)
2748
+ temp_data = typhoon_data[time_filter].groupby('SID').agg({
2749
+ 'USA_PRES': 'min', 'NAME': 'first', 'SEASON': 'first', 'ISO_TIME': 'first', 'USA_WIND': 'max'
2750
+ }).reset_index()
2751
+ temp_data['ONI'] = 0.0 # Default neutral
2752
+ temp_data['Category'] = temp_data['USA_WIND'].apply(categorize_typhoon_enhanced)
2753
+ temp_data['Year'] = temp_data['ISO_TIME'].dt.year
2754
+ temp_data['ENSO_Phase'] = 'Neutral'
2755
+ filtered_data = temp_data
2756
+ else:
2757
+ return go.Figure(), "No time data available for analysis"
2758
+
2759
+ if filtered_data.empty:
2760
+ return go.Figure(), "No data available for selected time period"
2761
 
2762
  fig = px.scatter(filtered_data, x='ONI', y='USA_PRES', color='Category',
2763
  hover_data=['NAME','Year','Category'],
2764
+ title='Pressure vs ONI (All Available Data)',
2765
  labels={'ONI':'ONI Value','USA_PRES':'Min Pressure (hPa)'},
2766
  color_discrete_map=enhanced_color_map)
2767
 
 
2775
  text=filtered_data.loc[mask,'NAME']+' ('+filtered_data.loc[mask,'Year'].astype(str)+')'
2776
  ))
2777
 
2778
+ # Try regression analysis if we have sufficient data
2779
+ try:
2780
+ if len(filtered_data) > 10:
2781
+ regression = perform_pressure_regression(start_year, start_month, end_year, end_month)
2782
+ else:
2783
+ regression = f"Pressure Analysis: {len(filtered_data)} storms analyzed (insufficient for regression)"
2784
+ except:
2785
+ regression = f"Pressure Analysis: {len(filtered_data)} storms analyzed"
2786
+
2787
  return fig, regression
2788
 
2789
  def get_longitude_analysis(start_year, start_month, end_year, end_month, enso_phase, typhoon_search):
2790
+ """FIXED: Longitude analysis that works with all data"""
2791
  start_date = datetime(start_year, start_month, 1)
2792
  end_date = datetime(end_year, end_month, 28)
2793
+
2794
+ if merged_data is not None and not merged_data.empty:
2795
+ filtered_data = merged_data[(merged_data['ISO_TIME']>=start_date) & (merged_data['ISO_TIME']<=end_date)].copy()
2796
+ filtered_data['ENSO_Phase'] = filtered_data['ONI'].apply(classify_enso_phases)
2797
+
2798
+ if enso_phase != 'all':
2799
+ filtered_data = filtered_data[filtered_data['ENSO_Phase'] == enso_phase.capitalize()]
2800
+ else:
2801
+ # Create filtered data from typhoon_data
2802
+ if 'ISO_TIME' in typhoon_data.columns:
2803
+ time_filter = (typhoon_data['ISO_TIME'] >= start_date) & (typhoon_data['ISO_TIME'] <= end_date)
2804
+ temp_data = typhoon_data[time_filter].groupby('SID').agg({
2805
+ 'LON': 'first', 'NAME': 'first', 'SEASON': 'first', 'ISO_TIME': 'first'
2806
+ }).reset_index()
2807
+ temp_data['ONI'] = 0.0 # Default neutral
2808
+ temp_data['Year'] = temp_data['ISO_TIME'].dt.year
2809
+ filtered_data = temp_data
2810
+ else:
2811
+ return go.Figure(), "No time data available", "No longitude analysis available"
2812
+
2813
+ if filtered_data.empty:
2814
+ return go.Figure(), "No data available", "No longitude analysis available"
2815
 
2816
  fig = px.scatter(filtered_data, x='LON', y='ONI', hover_data=['NAME'],
2817
+ title='Typhoon Generation Longitude vs ONI (All Available Data)')
2818
 
2819
+ slopes_text = f"Longitude Analysis: {len(filtered_data)} storms analyzed"
2820
+ regression = f"Data points: {len(filtered_data)}"
2821
+
2822
+ if len(filtered_data) > 10:
2823
  try:
2824
+ X = np.array(filtered_data['LON']).reshape(-1,1)
2825
+ y = filtered_data['ONI']
2826
  model = sm.OLS(y, sm.add_constant(X)).fit()
2827
  y_pred = model.predict(sm.add_constant(X))
2828
  fig.add_trace(go.Scatter(x=filtered_data['LON'], y=y_pred, mode='lines', name='Regression Line'))
2829
  slope = model.params[1]
2830
+ slopes_text = f"All Years Slope: {slope:.4f} (n={len(filtered_data)})"
2831
  except Exception as e:
2832
  slopes_text = f"Regression Error: {e}"
2833
+
2834
+ try:
2835
+ regression = perform_longitude_regression(start_year, start_month, end_year, end_month)
2836
+ except:
2837
+ regression = f"Longitude Analysis: {len(filtered_data)} storms analyzed"
2838
 
 
2839
  return fig, slopes_text, regression
2840
 
2841
  # -----------------------------
2842
  # ENHANCED: Animation Functions with Taiwan Standard Support - FIXED VERSION
2843
  # -----------------------------
2844
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2845
  def update_typhoon_options_enhanced(year, basin):
2846
  """Enhanced typhoon options with TD support and 2025 data"""
2847
  try:
 
3100
  return generate_enhanced_track_video_fixed(year, typhoon, standard)
3101
 
3102
  # -----------------------------
3103
+ # Load & Process Data - FIXED INITIALIZATION
3104
  # -----------------------------
3105
 
3106
  # Global variables initialization
 
3109
  merged_data = None
3110
 
3111
  def initialize_data():
3112
+ """FIXED: Initialize all data safely - LOAD ALL AVAILABLE DATA"""
3113
  global oni_data, typhoon_data, merged_data
3114
  try:
3115
+ logging.info("Starting comprehensive data loading process...")
3116
+
3117
+ # Try to load ONI data (optional)
3118
+ try:
3119
+ update_oni_data()
3120
+ if os.path.exists(ONI_DATA_PATH):
3121
+ oni_data = pd.read_csv(ONI_DATA_PATH)
3122
+ logging.info(f"ONI data loaded: {len(oni_data)} years")
3123
+ else:
3124
+ logging.warning("ONI data not available")
3125
+ oni_data = None
3126
+ except Exception as e:
3127
+ logging.warning(f"ONI data loading failed: {e}")
3128
+ oni_data = None
3129
+
3130
+ # Load typhoon data (required)
3131
+ temp_oni = oni_data if oni_data is not None else pd.DataFrame()
3132
+ temp_oni, typhoon_data = load_data_fixed(ONI_DATA_PATH, TYPHOON_DATA_PATH)
3133
+
3134
+ if oni_data is None:
3135
+ oni_data = temp_oni
3136
 
3137
+ if typhoon_data is not None and not typhoon_data.empty:
3138
  oni_long = process_oni_data(oni_data)
3139
  typhoon_max = process_typhoon_data(typhoon_data)
3140
  merged_data = merge_data(oni_long, typhoon_max)
3141
+
3142
+ logging.info(f"Data loading complete:")
3143
+ logging.info(f" - ONI data: {len(oni_data) if oni_data is not None else 0} years")
3144
+ logging.info(f" - Typhoon data: {len(typhoon_data)} records")
3145
+ logging.info(f" - Merged data: {len(merged_data)} storms")
3146
+
3147
+ # Log basin distribution
3148
+ if 'BASIN' in typhoon_data.columns:
3149
+ basin_counts = typhoon_data['BASIN'].value_counts()
3150
+ logging.info(f" - Basin distribution: {dict(basin_counts)}")
3151
+
3152
  else:
3153
+ logging.error("Failed to load typhoon data")
3154
+ # Create comprehensive fallback data
3155
+ oni_data = pd.DataFrame({'Year': range(1851, 2026), 'Jan': [0]*175, 'Feb': [0]*175, 'Mar': [0]*175, 'Apr': [0]*175,
3156
+ 'May': [0]*175, 'Jun': [0]*175, 'Jul': [0]*175, 'Aug': [0]*175, 'Sep': [0]*175,
3157
+ 'Oct': [0]*175, 'Nov': [0]*175, 'Dec': [0]*175})
3158
+ typhoon_data = create_comprehensive_fallback_typhoon_data()
3159
  oni_long = process_oni_data(oni_data)
3160
  typhoon_max = process_typhoon_data(typhoon_data)
3161
  merged_data = merge_data(oni_long, typhoon_max)
3162
+
3163
  except Exception as e:
3164
  logging.error(f"Error during data initialization: {e}")
3165
+ # Create comprehensive fallback data
3166
+ oni_data = pd.DataFrame({'Year': range(1851, 2026), 'Jan': [0]*175, 'Feb': [0]*175, 'Mar': [0]*175, 'Apr': [0]*175,
3167
+ 'May': [0]*175, 'Jun': [0]*175, 'Jul': [0]*175, 'Aug': [0]*175, 'Sep': [0]*175,
3168
+ 'Oct': [0]*175, 'Nov': [0]*175, 'Dec': [0]*175})
3169
+ typhoon_data = create_comprehensive_fallback_typhoon_data()
3170
  oni_long = process_oni_data(oni_data)
3171
  typhoon_max = process_typhoon_data(typhoon_data)
3172
  merged_data = merge_data(oni_long, typhoon_max)
 
3210
  This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
3211
 
3212
  ### πŸš€ Enhanced Features:
3213
+ - **All Basin Coverage**: Loads data from ALL IBTrACS basins (WP, EP, NA, SP, SI, NI)
3214
+ - **Complete Historical Range**: Full coverage from 1851-2025 (175+ years)
3215
+ - **ONI Independent**: Analysis works with or without ONI data
3216
  - **Advanced ML Clustering**: UMAP/t-SNE storm pattern analysis with separate visualizations
3217
  - **Predictive Routing**: Advanced storm track and intensity forecasting with uncertainty quantification
3218
  - **Complete TD Support**: Now includes Tropical Depressions (< 34 kt)
3219
  - **Taiwan Standard**: Full support for Taiwan meteorological classification system
 
3220
  - **Enhanced Animations**: High-quality storm track visualizations with both standards
3221
 
3222
  ### πŸ“Š Data Status:
3223
+ - **ONI Data**: {len(oni_data) if oni_data is not None else 0} years loaded
3224
  - **Typhoon Data**: {total_records:,} records loaded
3225
  - **Merged Data**: {len(merged_data):,} typhoons with ONI values
3226
  - **Available Years**: {year_range_display}
3227
+ - **Basin Coverage**: All IBTrACS basins (WP, EP, NA, SP, SI, NI)
3228
 
3229
  ### πŸ”§ Technical Capabilities:
3230
  - **UMAP Clustering**: {"βœ… Available" if UMAP_AVAILABLE else "⚠️ Limited to t-SNE/PCA"}
3231
  - **AI Predictions**: {"🧠 Deep Learning" if CNN_AVAILABLE else "πŸ”¬ Physics-based"}
3232
  - **Enhanced Categorization**: Tropical Depression to Super Typhoon
3233
  - **Platform**: Optimized for Hugging Face Spaces
3234
+ - **Maximum Data Utilization**: All available storms loaded regardless of ONI
3235
 
3236
  ### πŸ“ˆ Research Applications:
3237
+ - Climate change impact studies across all basins
3238
  - Seasonal forecasting research
3239
  - Storm pattern classification
3240
  - ENSO-typhoon relationship analysis
3241
  - Intensity prediction model development
3242
+ - Cross-regional classification comparisons
3243
+ - Historical trend analysis (1851-2025)
3244
  """
3245
  gr.Markdown(overview_text)
3246
 
 
3299
 
3300
  cluster_info_text = """
3301
  ### πŸ“Š Enhanced Clustering Features:
3302
+ - **All Basin Analysis**: Uses data from all global tropical cyclone basins
3303
+ - **Complete Historical Coverage**: Analyzes patterns from 1851-2025
3304
  - **Separate Visualizations**: Four distinct plots for comprehensive analysis
3305
  - **Multi-dimensional Analysis**: Uses 15+ storm characteristics including intensity, track shape, genesis location
3306
  - **Route Visualization**: Geographic storm tracks colored by cluster membership
 
3372
  label="Forecast Length (hours)",
3373
  value=72,
3374
  minimum=20,
3375
+ maximum=1000,
3376
  step=6,
3377
+ info="Extended forecasting: 20-1000 hours (42 days max)"
3378
  )
3379
  advanced_physics = gr.Checkbox(
3380
  label="Advanced Physics",
 
3529
  )
3530
  basin_dropdown = gr.Dropdown(
3531
  label="Basin",
3532
+ choices=["All Basins", "WP - Western Pacific", "EP - Eastern Pacific", "NA - North Atlantic",
3533
+ "SP - South Pacific", "SI - South Indian", "NI - North Indian"],
3534
  value="All Basins"
3535
  )
3536
 
 
3564
  # FIXED animation info text with corrected Taiwan standards
3565
  animation_info_text = """
3566
  ### 🎬 Enhanced Animation Features:
3567
+ - **All Basin Support**: Visualize storms from any global basin (WP, EP, NA, SP, SI, NI)
3568
+ - **Complete Historical Range**: Animate storms from 1851-2025
3569
  - **Dual Standards**: Full support for both Atlantic and Taiwan classification systems
3570
  - **Full TD Support**: Now displays Tropical Depressions (< 34 kt) in gray
 
3571
  - **Enhanced Maps**: Better cartographic projections with terrain features
3572
  - **Smart Scaling**: Storm symbols scale dynamically with intensity
3573
  - **Real-time Info**: Live position, time, and meteorological data display
 
3605
  fig_dist = px.bar(
3606
  x=cat_counts.index,
3607
  y=cat_counts.values,
3608
+ title="Storm Intensity Distribution (All Basins - Including Tropical Depressions)",
3609
  labels={'x': 'Category', 'y': 'Number of Storms'},
3610
  color=cat_counts.index,
3611
  color_discrete_map=enhanced_color_map
 
3620
  fig_seasonal = px.bar(
3621
  x=monthly_counts.index,
3622
  y=monthly_counts.values,
3623
+ title="Seasonal Storm Distribution (All Basins)",
3624
  labels={'x': 'Month', 'y': 'Number of Storms'},
3625
  color=monthly_counts.values,
3626
  color_continuous_scale='Viridis'
 
3629
  fig_seasonal = None
3630
 
3631
  # Basin distribution
3632
+ if 'BASIN' in typhoon_data.columns:
3633
+ basin_data = typhoon_data['BASIN'].value_counts()
3634
  fig_basin = px.pie(
3635
  values=basin_data.values,
3636
  names=basin_data.index,
3637
+ title="Distribution by Basin (All Global Basins)"
3638
  )
3639
  else:
3640
  fig_basin = None
 
3653
  except Exception as e:
3654
  gr.Markdown(f"Visualization error: {str(e)}")
3655
 
3656
+ # Enhanced statistics - FIXED formatting with ALL DATA
3657
  total_storms = len(typhoon_data['SID'].unique()) if 'SID' in typhoon_data.columns else 0
3658
  total_records = len(typhoon_data)
3659
 
 
3664
  year_range = f"{min_year}-{max_year}"
3665
  years_covered = typhoon_data['SEASON'].nunique()
3666
  except (ValueError, TypeError):
3667
+ year_range = "1851-2025"
3668
+ years_covered = 175
3669
  else:
3670
+ year_range = "1851-2025"
3671
+ years_covered = 175
3672
 
3673
+ if 'BASIN' in typhoon_data.columns:
3674
  try:
3675
+ basins_available = ', '.join(sorted(typhoon_data['BASIN'].unique()))
3676
  avg_storms_per_year = total_storms / max(years_covered, 1)
3677
  except Exception:
3678
+ basins_available = "WP, EP, NA, SP, SI, NI"
3679
  avg_storms_per_year = 0
3680
  else:
3681
+ basins_available = "WP, EP, NA, SP, SI, NI"
3682
  avg_storms_per_year = 0
3683
 
3684
  # TD specific statistics
 
3711
  - **Typhoons (C1-C5)**: {typhoon_storms:,} storms
3712
 
3713
  ### πŸš€ Platform Capabilities:
3714
+ - **Complete Global Coverage** - ALL IBTrACS basins loaded (WP, EP, NA, SP, SI, NI)
3715
+ - **Maximum Historical Range** - Full 175+ year coverage (1851-2025)
3716
+ - **ONI Independence** - All storm data preserved regardless of ONI availability
3717
  - **Complete TD Analysis** - First platform to include comprehensive TD tracking
3718
  - **Dual Classification Systems** - Both Atlantic and Taiwan standards supported
3719
  - **Advanced ML Clustering** - DBSCAN pattern recognition with separate visualizations
3720
  - **Real-time Predictions** - Physics-based and optional CNN intensity forecasting
 
3721
  - **Enhanced Animations** - Professional-quality storm track videos
3722
+ - **Cross-basin Analysis** - Comprehensive global tropical cyclone coverage
3723
 
3724
  ### πŸ”¬ Research Applications:
3725
+ - Global climate change impact studies
3726
+ - Cross-basin seasonal forecasting research
3727
+ - Storm pattern classification across all oceans
3728
  - ENSO-typhoon relationship analysis
3729
  - Intensity prediction model development
3730
  - Cross-regional classification comparisons
3731
+ - Historical trend analysis spanning 175+ years
3732
+ - Basin interaction and teleconnection studies
3733
  """
3734
  gr.Markdown(stats_text)
3735
 
 
3788
  File Checks:
3789
  - ONI Path Exists: {os.path.exists(ONI_DATA_PATH)}
3790
  - Typhoon Path Exists: {os.path.exists(TYPHOON_DATA_PATH)}
3791
+
3792
+ Basin Files Available:
3793
+ {[f"- {basin}: {BASIN_FILES[basin]}" for basin in BASIN_FILES.keys()]}
3794
  """
3795
  return debug_text
3796