RFTSystems commited on
Commit
f932dcc
·
verified ·
1 Parent(s): 754b80e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -59
app.py CHANGED
@@ -1,6 +1,6 @@
1
- # app.py
2
  # ===============================================================
3
- # Rendered Frame Theory — Live Prediction Console
4
  # Domains: Atmospheric / Seismic / Magnetic / Solar
5
  # Full transparency: exact inputs + computed z, τ_eff, Ω_obs, α_R, index, and decision rule.
6
  # Single-file.
@@ -14,20 +14,14 @@ import httpx
14
  import numpy as np
15
  import pandas as pd
16
 
17
- APP_NAME = "Rendered Frame Theory — Live Prediction Console"
18
  UA = {"User-Agent": "RFTSystems/LivePredictionConsole"}
19
 
20
- # ---------------------------
21
- # Core constants
22
- # ---------------------------
23
  T_EARTH = 365.2422 * 24 * 3600.0
24
  OMEGA_OBS = 2.0 * math.pi / T_EARTH
25
  K_TAU = 1.38
26
  ALPHA_R = 1.02
27
 
28
- # ---------------------------
29
- # Regions
30
- # ---------------------------
31
  REGION_BBOX = {
32
  "Global": None,
33
  "EMEA": (-35.0, -20.0, 70.0, 60.0),
@@ -40,30 +34,30 @@ RING_OF_FIRE_BBOXES = [
40
  (10.0, -90.0, 60.0, -60.0),
41
  ]
42
 
43
- # ---------------------------
44
- # Helpers
45
- # ---------------------------
46
  def utc_now_iso() -> str:
47
  return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
48
 
 
49
  def clamp(x: float, a: float, b: float) -> float:
50
  return max(a, min(b, x))
51
 
 
52
  def tau_eff_from_z(z: float) -> float:
53
  z = max(0.0, float(z))
54
  return K_TAU * math.log(1.0 + z)
55
 
 
56
  def stable_log_ratio(x: float, x0: float) -> float:
57
  x = max(float(x), 1e-30)
58
  x0 = max(float(x0), 1e-30)
59
  return math.log(x / x0)
60
 
 
61
  def index_from_tau(tau: float) -> float:
62
  return float(OMEGA_OBS * float(tau) * ALPHA_R)
63
 
64
- # ---------------------------
65
- # Live adapters
66
- # ---------------------------
67
  def geocode_location(q: str):
68
  q = (q or "").strip()
69
  if not q:
@@ -82,6 +76,7 @@ def geocode_location(q: str):
82
  display = f"{top.get('name','')}, {top.get('country_code','')}".strip().strip(",")
83
  return lat, lon, display
84
 
 
85
  def fetch_openmeteo_hourly(lat: float, lon: float, past_days: int = 1):
86
  url = "https://api.open-meteo.com/v1/forecast"
87
  params = {
@@ -104,6 +99,7 @@ def fetch_openmeteo_hourly(lat: float, lon: float, past_days: int = 1):
104
  "wind": hourly.get("wind_speed_10m") or [],
105
  }
106
 
 
107
  def fetch_kp_last_24h():
108
  url = "https://services.swpc.noaa.gov/json/planetary_k_index_1m.json"
109
  r = httpx.get(url, headers=UA, timeout=15)
@@ -122,6 +118,7 @@ def fetch_kp_last_24h():
122
  pass
123
  return vals[-1440:]
124
 
 
125
  def fetch_goes_xray_1day():
126
  url = "https://services.swpc.noaa.gov/json/goes/primary/xrays-1-day.json"
127
  r = httpx.get(url, headers=UA, timeout=15)
@@ -140,6 +137,7 @@ def fetch_goes_xray_1day():
140
  pass
141
  return out
142
 
 
143
  def fetch_usgs_quakes(hours: int, minmag: float, bbox=None):
144
  url = "https://earthquake.usgs.gov/fdsnws/event/1/query"
145
  end = datetime.now(timezone.utc)
@@ -173,9 +171,7 @@ def fetch_usgs_quakes(hours: int, minmag: float, bbox=None):
173
  out.append({"id": f.get("id"), "mag": props.get("mag"), "place": props.get("place"), "time": props.get("time")})
174
  return out
175
 
176
- # ---------------------------
177
- # Agents
178
- # ---------------------------
179
  def magnetic_agent():
180
  kp = fetch_kp_last_24h()
181
  if len(kp) < 30:
@@ -216,10 +212,15 @@ def magnetic_agent():
216
  "live_status": live,
217
  "truth_source": "NOAA SWPC planetary_k_index_1m (global)",
218
  "inputs_used": {"kp_last": last, "kp_drift": drift, "kp_slope": slope, "tail_len": len(tail)},
 
 
 
 
219
  "why": "z_mag compresses magnitude+variability into a bounded stress coordinate; τ_eff rises as ln(1+z).",
220
  "how": "Fetch Kp → compute last/variability/slope → z_mag → τ_eff=1.38 ln(1+z) → Index=Ω_obs·τ_eff·α_R → label via fixed thresholds.",
221
  }
222
 
 
223
  def solar_agent():
224
  flux = fetch_goes_xray_1day()
225
  if len(flux) < 50:
@@ -260,10 +261,15 @@ def solar_agent():
260
  "live_status": live,
261
  "truth_source": "NOAA SWPC GOES primary xrays-1-day (global)",
262
  "inputs_used": {"flux_mean": f_mean, "flux_peak": f_peak, "tail_len": len(tail)},
 
 
 
 
263
  "why": "z_solar is derived from flux relative to baseline via a log ratio; τ_eff rises as ln(1+z).",
264
  "how": "Fetch GOES flux → mean/peak → z_solar=clamp(ln(F_mean/1e-8)/10) → τ_eff → Index → label via fixed thresholds.",
265
  }
266
 
 
267
  def atmospheric_agent(lat: float, lon: float, display: str):
268
  wx = fetch_openmeteo_hourly(lat, lon, past_days=1)
269
  temp = wx["temp"]
@@ -325,10 +331,15 @@ def atmospheric_agent(lat: float, lon: float, display: str):
325
  "live_status": live,
326
  "truth_source": "Open-Meteo hourly (location-based)",
327
  "inputs_used": {"dT_12h": dT, "dP_12h": dp, "wind_mean": w_mean, "lat": lat, "lon": lon},
 
 
 
 
328
  "why": "z_atm is derived from thermal swing and pressure change as a bounded stress coordinate; τ_eff rises as ln(1+z).",
329
  "how": "Geocode → fetch hourly series → compute ΔT and ΔP (last ~12h) → z_atm → τ_eff → Index → label via fixed thresholds.",
330
  }
331
 
 
332
  def seismic_agent(region: str):
333
  if region == "RingOfFire":
334
  seen = set()
@@ -391,13 +402,15 @@ def seismic_agent(region: str):
391
  "live_status": live,
392
  "truth_source": "USGS FDSN event feed (region-filtered)",
393
  "inputs_used": {"count_24h": N, "max_mag_24h": Mmax, "region": region},
 
 
 
 
394
  "why": "z_seis compresses activity density and severity into a bounded stress coordinate; τ_eff rises as ln(1+z).",
395
  "how": "Fetch USGS in region → count + max magnitude → z_seis → τ_eff → Index → label via fixed thresholds.",
396
  }
397
 
398
- # ---------------------------
399
- # Orchestrator
400
- # ---------------------------
401
  def run_forecast(location_text: str, seismic_region: str):
402
  try:
403
  lat, lon, display = geocode_location(location_text)
@@ -459,72 +472,132 @@ def run_forecast(location_text: str, seismic_region: str):
459
  header = f"**Location:** {display} (lat {lat:.3f}, lon {lon:.3f}) | **UTC:** {ts}"
460
  return header, df, atm, sei, mag, sol
461
 
462
- # ---------------------------
463
- # Open Method
464
- # ---------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  METHOD_MD = f"""
466
- ## Open method
467
-
468
- **Shared core:**
469
- - _eff = {K_TAU} · ln(1 + z)`
470
- - _obs = 2π / T_earth = {OMEGA_OBS:.6e}`
471
- - _R = {ALPHA_R}`
472
- - `Index = Ω_obs · τ_eff · α_R`
473
-
474
- ### Atmospheric (Open-Meteo, location)
475
- - Inputs: hourly temp, pressure, wind (last ~12h).
476
- - `ΔT = max(T) - min(T)`
477
- - `ΔP = P_end - P_start` (if available)
478
- - `z_atm = clamp( clamp(ΔT/10,0..2) + clamp(|ΔP|/12,0..1.5), 0..3 )`
479
- - Label rule in agent output.
480
-
481
- ### Seismic (USGS, region)
482
- - Inputs: USGS events in last 24h (M≥2.5), filtered by region.
483
- - Count `N`, max magnitude `Mmax`
484
- - `z_seis = clamp( clamp(N/60,0..1.5) + clamp(max(0,Mmax-4)/2.5,0..1.5), 0..3 )`
485
- - Label rule in agent output.
486
-
487
- ### Magnetic (NOAA Kp, global)
488
- - Inputs: planetary Kp 1-min stream.
489
- - `z_mag = clamp( (Kp_last/9) + (drift/2) + 2·|slope|, 0..3 )`
490
- - Label rule in agent output.
491
-
492
- ### Solar (GOES X-ray, global)
493
- - Inputs: GOES 1–8Å flux (1-day stream).
494
- - `z_solar = clamp( ln(F_mean/1e-8) / 10, 0..3 )`
495
- - Label rule in agent output.
496
-
497
- Missing/short feeds show **DISABLED**.
498
  """
499
 
500
- # ---------------------------
501
- # UI
502
- # ---------------------------
503
  with gr.Blocks(title=APP_NAME) as demo:
504
  gr.Markdown(f"# {APP_NAME}")
505
 
506
  with gr.Tab("Live Forecast"):
507
  with gr.Row():
508
  loc = gr.Textbox(label="Location", value="London")
 
 
 
 
 
 
 
 
 
509
  region = gr.Dropdown(["Global", "EMEA", "AMER", "APAC", "RingOfFire"], value="EMEA", label="Seismic Region")
 
 
 
 
 
 
510
 
511
  btn = gr.Button("Run Forecast", variant="primary")
 
 
 
 
 
 
 
 
512
  header_md = gr.Markdown()
 
 
 
 
 
 
513
  table = gr.Dataframe(headers=["Domain", "RFT Prediction", "Live Status"], interactive=False)
514
 
515
  with gr.Accordion("Atmospheric details", open=False):
 
516
  atm_json = gr.JSON(label="Atmospheric agent output")
517
  with gr.Accordion("Seismic details", open=False):
 
518
  sei_json = gr.JSON(label="Seismic agent output")
519
  with gr.Accordion("Magnetic details", open=False):
 
520
  mag_json = gr.JSON(label="Magnetic agent output")
521
  with gr.Accordion("Solar details", open=False):
 
522
  sol_json = gr.JSON(label="Solar agent output")
523
 
524
  btn.click(run_forecast, inputs=[loc, region], outputs=[header_md, table, atm_json, sei_json, mag_json, sol_json])
525
 
526
  with gr.Tab("Method (Open)"):
 
527
  gr.Markdown(METHOD_MD)
528
 
 
529
  if __name__ == "__main__":
530
  demo.launch()
 
1
+
2
  # ===============================================================
3
+ # Rendered Frame Theory — Live Prediction Console (Open Method)
4
  # Domains: Atmospheric / Seismic / Magnetic / Solar
5
  # Full transparency: exact inputs + computed z, τ_eff, Ω_obs, α_R, index, and decision rule.
6
  # Single-file.
 
14
  import numpy as np
15
  import pandas as pd
16
 
17
+ APP_NAME = "Rendered Frame Theory — Live Prediction Console (Open Method)"
18
  UA = {"User-Agent": "RFTSystems/LivePredictionConsole"}
19
 
 
 
 
20
  T_EARTH = 365.2422 * 24 * 3600.0
21
  OMEGA_OBS = 2.0 * math.pi / T_EARTH
22
  K_TAU = 1.38
23
  ALPHA_R = 1.02
24
 
 
 
 
25
  REGION_BBOX = {
26
  "Global": None,
27
  "EMEA": (-35.0, -20.0, 70.0, 60.0),
 
34
  (10.0, -90.0, 60.0, -60.0),
35
  ]
36
 
37
+
 
 
38
  def utc_now_iso() -> str:
39
  return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
40
 
41
+
42
  def clamp(x: float, a: float, b: float) -> float:
43
  return max(a, min(b, x))
44
 
45
+
46
  def tau_eff_from_z(z: float) -> float:
47
  z = max(0.0, float(z))
48
  return K_TAU * math.log(1.0 + z)
49
 
50
+
51
  def stable_log_ratio(x: float, x0: float) -> float:
52
  x = max(float(x), 1e-30)
53
  x0 = max(float(x0), 1e-30)
54
  return math.log(x / x0)
55
 
56
+
57
  def index_from_tau(tau: float) -> float:
58
  return float(OMEGA_OBS * float(tau) * ALPHA_R)
59
 
60
+
 
 
61
  def geocode_location(q: str):
62
  q = (q or "").strip()
63
  if not q:
 
76
  display = f"{top.get('name','')}, {top.get('country_code','')}".strip().strip(",")
77
  return lat, lon, display
78
 
79
+
80
  def fetch_openmeteo_hourly(lat: float, lon: float, past_days: int = 1):
81
  url = "https://api.open-meteo.com/v1/forecast"
82
  params = {
 
99
  "wind": hourly.get("wind_speed_10m") or [],
100
  }
101
 
102
+
103
  def fetch_kp_last_24h():
104
  url = "https://services.swpc.noaa.gov/json/planetary_k_index_1m.json"
105
  r = httpx.get(url, headers=UA, timeout=15)
 
118
  pass
119
  return vals[-1440:]
120
 
121
+
122
  def fetch_goes_xray_1day():
123
  url = "https://services.swpc.noaa.gov/json/goes/primary/xrays-1-day.json"
124
  r = httpx.get(url, headers=UA, timeout=15)
 
137
  pass
138
  return out
139
 
140
+
141
  def fetch_usgs_quakes(hours: int, minmag: float, bbox=None):
142
  url = "https://earthquake.usgs.gov/fdsnws/event/1/query"
143
  end = datetime.now(timezone.utc)
 
171
  out.append({"id": f.get("id"), "mag": props.get("mag"), "place": props.get("place"), "time": props.get("time")})
172
  return out
173
 
174
+
 
 
175
  def magnetic_agent():
176
  kp = fetch_kp_last_24h()
177
  if len(kp) < 30:
 
212
  "live_status": live,
213
  "truth_source": "NOAA SWPC planetary_k_index_1m (global)",
214
  "inputs_used": {"kp_last": last, "kp_drift": drift, "kp_slope": slope, "tail_len": len(tail)},
215
+ "location_effect": "Location input does not change Magnetic. Kp is a global index.",
216
+ "do": "Use this to track global geomagnetic storm regime shifts.",
217
+ "dont": "Do not treat this as a city-level magnetometer or outage predictor.",
218
+ "what_it_is_not": "Not a local geomagnetic forecast. Not a power-grid impact model.",
219
  "why": "z_mag compresses magnitude+variability into a bounded stress coordinate; τ_eff rises as ln(1+z).",
220
  "how": "Fetch Kp → compute last/variability/slope → z_mag → τ_eff=1.38 ln(1+z) → Index=Ω_obs·τ_eff·α_R → label via fixed thresholds.",
221
  }
222
 
223
+
224
  def solar_agent():
225
  flux = fetch_goes_xray_1day()
226
  if len(flux) < 50:
 
261
  "live_status": live,
262
  "truth_source": "NOAA SWPC GOES primary xrays-1-day (global)",
263
  "inputs_used": {"flux_mean": f_mean, "flux_peak": f_peak, "tail_len": len(tail)},
264
+ "location_effect": "Location input does not change Solar. GOES flux is global.",
265
+ "do": "Use this to track global solar radiative regime changes.",
266
+ "dont": "Do not treat this as a flare time predictor or CME trajectory forecast.",
267
+ "what_it_is_not": "Not a flare timing model. Not a CME arrival model.",
268
  "why": "z_solar is derived from flux relative to baseline via a log ratio; τ_eff rises as ln(1+z).",
269
  "how": "Fetch GOES flux → mean/peak → z_solar=clamp(ln(F_mean/1e-8)/10) → τ_eff → Index → label via fixed thresholds.",
270
  }
271
 
272
+
273
  def atmospheric_agent(lat: float, lon: float, display: str):
274
  wx = fetch_openmeteo_hourly(lat, lon, past_days=1)
275
  temp = wx["temp"]
 
331
  "live_status": live,
332
  "truth_source": "Open-Meteo hourly (location-based)",
333
  "inputs_used": {"dT_12h": dT, "dP_12h": dp, "wind_mean": w_mean, "lat": lat, "lon": lon},
334
+ "location_effect": "Location input changes Atmospheric because it queries the selected lat/lon.",
335
+ "do": "Use this as a short-term stability detector from temperature swing and pressure change.",
336
+ "dont": "Do not treat this as a rain/snow probability forecast or a full numerical weather model.",
337
+ "what_it_is_not": "Not a precipitation forecast. Not a synoptic model. Not a radar nowcast.",
338
  "why": "z_atm is derived from thermal swing and pressure change as a bounded stress coordinate; τ_eff rises as ln(1+z).",
339
  "how": "Geocode → fetch hourly series → compute ΔT and ΔP (last ~12h) → z_atm → τ_eff → Index → label via fixed thresholds.",
340
  }
341
 
342
+
343
  def seismic_agent(region: str):
344
  if region == "RingOfFire":
345
  seen = set()
 
402
  "live_status": live,
403
  "truth_source": "USGS FDSN event feed (region-filtered)",
404
  "inputs_used": {"count_24h": N, "max_mag_24h": Mmax, "region": region},
405
+ "location_effect": "Location input does not change Seismic in region mode. It filters by selected region, not by city.",
406
+ "do": "Use this as a regional seismic stress monitor (regime detection).",
407
+ "dont": "Do not treat this as a time-and-epicenter earthquake prediction system.",
408
+ "what_it_is_not": "Not an earthquake time predictor. Not a rupture location predictor.",
409
  "why": "z_seis compresses activity density and severity into a bounded stress coordinate; τ_eff rises as ln(1+z).",
410
  "how": "Fetch USGS in region → count + max magnitude → z_seis → τ_eff → Index → label via fixed thresholds.",
411
  }
412
 
413
+
 
 
414
  def run_forecast(location_text: str, seismic_region: str):
415
  try:
416
  lat, lon, display = geocode_location(location_text)
 
472
  header = f"**Location:** {display} (lat {lat:.3f}, lon {lon:.3f}) | **UTC:** {ts}"
473
  return header, df, atm, sei, mag, sol
474
 
475
+
476
+ INSTRUCTIONS_MD = """
477
+ ## Use and interpretation
478
+
479
+ **Location input**
480
+ - Used for Atmospheric.
481
+ - Not used for Solar or Magnetic (global signals).
482
+ - Seismic is region-filtered (not city-level) in this build.
483
+
484
+ **Seismic region**
485
+ - Filters USGS earthquakes by large tectonic region.
486
+ - Counts are not “near your city” unless a radius mode is implemented.
487
+
488
+ **Run Forecast**
489
+ - Pulls live data and recomputes from scratch.
490
+ - No auto-refresh. No memory. No smoothing.
491
+
492
+ **Reading the table**
493
+ - RFT Prediction shows model state + index + z + τ_eff.
494
+ - Live Status shows the raw physical measurements used.
495
+ - DISABLED means missing/insufficient live data; no guessing is performed.
496
+ """
497
+
498
+ ATM_MD = """
499
+ ## Atmospheric
500
+ - This is a short-term stability detector from temperature swing and pressure change.
501
+ - This is not a precipitation forecast and not a full numerical weather model.
502
+ """
503
+
504
+ SEIS_MD = """
505
+ ## Seismic
506
+ - This is a regional seismic stress monitor from activity density and peak magnitude.
507
+ - This is not a time/epicenter prediction system.
508
+ """
509
+
510
+ MAG_MD = """
511
+ ## Magnetic
512
+ - This tracks global geomagnetic storm regime shifts using NOAA Kp.
513
+ - This is not a local magnetometer reading or grid-impact forecast.
514
+ """
515
+
516
+ SOL_MD = """
517
+ ## Solar
518
+ - This tracks global solar radiative regime shifts using GOES X-ray flux.
519
+ - This is not a flare timing predictor or CME arrival model.
520
+ """
521
+
522
  METHOD_MD = f"""
523
+ ## Open method equations
524
+
525
+ Shared core:
526
+ - τ_eff = {K_TAU} · ln(1 + z)
527
+ - Ω_obs = 2π / T_earth = {OMEGA_OBS:.6e}
528
+ - α_R = {ALPHA_R}
529
+ - Index = Ω_obs · τ_eff · α_R
530
+
531
+ z definitions:
532
+ - Atmospheric: z_atm = clamp( clamp(ΔT/10,0..2) + clamp(|ΔP|/12,0..1.5), 0..3 )
533
+ - Seismic: z_seis = clamp( clamp(N/60,0..1.5) + clamp(max(0,Mmax-4)/2.5,0..1.5), 0..3 )
534
+ - Magnetic: z_mag = clamp( (Kp_last/9) + (drift/2) + 2·|slope|, 0..3 )
535
+ - Solar: z_solar= clamp( ln(F_mean/1e-8)/10, 0..3 )
536
+
537
+ Decision thresholds are shown per-domain in the agent output under “rule_fired”.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  """
539
 
540
+
 
 
541
  with gr.Blocks(title=APP_NAME) as demo:
542
  gr.Markdown(f"# {APP_NAME}")
543
 
544
  with gr.Tab("Live Forecast"):
545
  with gr.Row():
546
  loc = gr.Textbox(label="Location", value="London")
547
+ gr.Markdown(
548
+ "**Location input**\n\n"
549
+ "- Used for Atmospheric.\n"
550
+ "- Not used for Solar or Magnetic (global signals).\n"
551
+ "- Seismic is region-filtered in this build.\n\n"
552
+ "If a location cannot be resolved, predictions are disabled instead of guessed."
553
+ )
554
+
555
+ with gr.Row():
556
  region = gr.Dropdown(["Global", "EMEA", "AMER", "APAC", "RingOfFire"], value="EMEA", label="Seismic Region")
557
+ gr.Markdown(
558
+ "**Seismic region selector**\n\n"
559
+ "- Filters USGS earthquakes by large region.\n"
560
+ "- Counts are not “near your city” unless radius mode is implemented.\n"
561
+ "- Not a time/epicenter prediction system."
562
+ )
563
 
564
  btn = gr.Button("Run Forecast", variant="primary")
565
+ gr.Markdown(
566
+ "**Run Forecast**\n\n"
567
+ "- Pulls live data and recomputes from scratch.\n"
568
+ "- No auto-refresh.\n"
569
+ "- No stored memory.\n"
570
+ "- No guessing when data is missing."
571
+ )
572
+
573
  header_md = gr.Markdown()
574
+ gr.Markdown(
575
+ "**How to read the table**\n\n"
576
+ "- RFT Prediction shows model state + index + z + τ_eff.\n"
577
+ "- Live Status shows the raw physical measurements used.\n"
578
+ "- DISABLED means missing/insufficient live data; no guessing is performed."
579
+ )
580
  table = gr.Dataframe(headers=["Domain", "RFT Prediction", "Live Status"], interactive=False)
581
 
582
  with gr.Accordion("Atmospheric details", open=False):
583
+ gr.Markdown(ATM_MD)
584
  atm_json = gr.JSON(label="Atmospheric agent output")
585
  with gr.Accordion("Seismic details", open=False):
586
+ gr.Markdown(SEIS_MD)
587
  sei_json = gr.JSON(label="Seismic agent output")
588
  with gr.Accordion("Magnetic details", open=False):
589
+ gr.Markdown(MAG_MD)
590
  mag_json = gr.JSON(label="Magnetic agent output")
591
  with gr.Accordion("Solar details", open=False):
592
+ gr.Markdown(SOL_MD)
593
  sol_json = gr.JSON(label="Solar agent output")
594
 
595
  btn.click(run_forecast, inputs=[loc, region], outputs=[header_md, table, atm_json, sei_json, mag_json, sol_json])
596
 
597
  with gr.Tab("Method (Open)"):
598
+ gr.Markdown(INSTRUCTIONS_MD)
599
  gr.Markdown(METHOD_MD)
600
 
601
+
602
  if __name__ == "__main__":
603
  demo.launch()