#!/usr/bin/env python3
"""
HK Weather Prediction Market — Dev Dashboard
Flask web server with interactive charts:
- Real-time HKO observations + WeatherNext forecasts
- Temperature, rain probability, wind speed charts
- HKO vs WeatherNext model comparison
- Trading signals + Kelly sizing panel
Usage:
python web_dashboard.py
# Open http://localhost:5000
"""
import json
import math
from datetime import datetime, timedelta
from pathlib import Path
from flask import Flask, jsonify, render_template_string
from weather.hk_extractor import HKExtractor
from weather.hko_client import HKOClient
from weather.openmeteo_client import OpenMeteoClient
from strategy.kelly import KellyCriterion
from config import HK_COORDS
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
try:
from ml import MLPredictor
_ml_available = True
except Exception:
_ml_available = False
app = Flask(__name__)
HTML_TEMPLATE = r'''
HK Weather Prediction Market — Dev Dashboard
Current Conditions HKO
Loading...
Temperature
--
Feels Like
--
Humidity
--
Wind
--
Pressure
--
Tomorrow
Temp Max
--
Temp Min
--
Rain Probability
--
Rain Total
--
Wind Max
--
Gusts Max
--
5-Day Temperature Forecast
Open-Meteo + HKO comparison
HKO vs WeatherNext Model Comparison
ML Model Predictions Logistic Regression
Loading...
Typhoon Probabilities Climatological
Loading...
Trading Signals
Computing...
Kelly Sizing Simulator fraction=0.25
Computing...
WeatherNext Raw Output (Open-Meteo)
Loading...
'''
@app.route("/api/dashboard")
def api_dashboard():
"""Return all dashboard data as JSON."""
try:
hko = HKOClient()
om = OpenMeteoClient()
forecast = om.get_forecast(lead_days=5)
current_data = {}
try:
hko_cur = hko.get_current_weather()
om_cur = om.get_current_conditions()
if hko_cur and hko_cur.get("temperature"):
current_data["temperature"] = hko_cur["temperature"][0]["value"]
else:
current_data["temperature"] = round(float(om_cur.get("temperature", 0)), 1)
if hko_cur and hko_cur.get("humidity"):
current_data["humidity"] = hko_cur["humidity"][0]["value"]
else:
current_data["humidity"] = round(float(om_cur.get("humidity", 0)), 1)
current_data["feels_like"] = round(float(om_cur.get("apparent_temp", 0)), 1)
current_data["wind_speed"] = round(float(om_cur.get("wind_speed", 0)), 1)
current_data["wind_direction"] = round(float(om_cur.get("wind_direction", 0)), 0)
current_data["pressure"] = round(float(om_cur.get("surface_pressure", 0)), 1)
w = hko_cur.get("warning_message", "") if hko_cur else ""
current_data["warning"] = w[0] if isinstance(w, list) and w else (w if isinstance(w, str) else "")
except Exception:
current_data = {"temperature": "--", "humidity": "--"}
typhoon_data = {"signal": hko.get_current_signal_level()}
tomorrow_data = {}
if forecast is not None and len(forecast) > 0:
d0 = forecast.iloc[0]
d1 = forecast.iloc[1] if len(forecast) > 1 else d0
tomorrow_data = {
"date": d1.name.strftime("%Y-%m-%d"),
"temp_max": round(float(d1.get("temperature_2m_max", 0)), 1),
"temp_min": round(float(d1.get("temperature_2m_min", 0)), 1),
"rain_prob": round(float(d1.get("precipitation_probability_max", 0)), 1),
"rain_sum": round(float(d1.get("precipitation_sum", 0)), 1),
"wind_max": round(float(d1.get("wind_speed_10m_max", 0)), 1),
"wind_gust": round(float(d1.get("wind_gusts_10m_max", 0)), 1),
}
fc_series = {"dates": [], "tmax": [], "tmin": [], "rain_prob": [],
"rain_sum": [], "wind_max": [], "wind_gust": [],
"hko_tmax": [], "hko_tmin": []}
if forecast is not None:
for idx, row in forecast.iterrows():
fc_series["dates"].append(idx.strftime("%m/%d"))
fc_series["tmax"].append(round(float(row.get("temperature_2m_max", 0)), 1))
fc_series["tmin"].append(round(float(row.get("temperature_2m_min", 0)), 1))
fc_series["rain_prob"].append(round(float(row.get("precipitation_probability_max", 0)), 1))
fc_series["rain_sum"].append(round(float(row.get("precipitation_sum", 0)), 1))
fc_series["wind_max"].append(round(float(row.get("wind_speed_10m_max", 0)), 1))
fc_series["wind_gust"].append(round(float(row.get("wind_gusts_10m_max", 0)), 1))
try:
hko_fc = hko.get_forecast()
if hko_fc:
for day in hko_fc[:5]:
fc_series["hko_tmax"].append(int(day.get("forecast_temp_max", 0)) if day.get("forecast_temp_max") else None)
fc_series["hko_tmin"].append(int(day.get("forecast_temp_min", 0)) if day.get("forecast_temp_min") else None)
while len(fc_series["hko_tmax"]) < len(fc_series["tmax"]):
fc_series["hko_tmax"].append(None)
fc_series["hko_tmin"].append(None)
except Exception:
pass
signals = []
if forecast is not None and len(forecast) > 1:
d1 = forecast.iloc[1]
tmrw = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
rain_p = round(float(d1.get("precipitation_probability_max", 50)), 1)
temp_p = round(float(d1.get("temperature_2m_max", 30)), 1)
signals = [
{
"question": f"Rain in HK on {tmrw}?",
"model_prob": rain_p,
"market_prob": 50.0,
"edge_bps": round((rain_p - 50) * 100, 0),
"signal": "buy_no" if rain_p < 45 else ("buy_yes" if rain_p > 55 else "pass"),
"size": "—"
},
{
"question": f"Temp > 35°C in HK on {tmrw}?",
"model_prob": min(95, max(5, 50 + (temp_p - 35) * 20)),
"market_prob": 40.0,
"edge_bps": round((min(95, max(5, 50 + (temp_p - 35) * 20)) - 40) * 100, 0),
"signal": "buy_yes" if temp_p >= 35 else "pass",
"size": "—"
},
{
"question": f"Temp > 33°C in HK on {tmrw}?",
"model_prob": min(95, max(5, 50 + (temp_p - 33) * 20)),
"market_prob": 70.0,
"edge_bps": round((min(95, max(5, 50 + (temp_p - 33) * 20)) - 70) * 100, 0),
"signal": "buy_yes" if temp_p >= 33 else "pass",
"size": "—"
},
]
kelly = KellyCriterion(bankroll_usdc=1000.0)
kelly_results = []
if forecast is not None and len(forecast) > 1:
d1 = forecast.iloc[1]
rain_p = round(float(d1.get("precipitation_probability_max", 50)), 1)
temp_p = round(float(d1.get("temperature_2m_max", 30)), 1)
scenarios = [
("Rain tomorrow", rain_p, 50),
("Temp > 35°C", min(95, max(5, 50 + (temp_p - 35) * 20)), 35),
("Temp > 33°C", min(95, max(5, 50 + (temp_p - 33) * 20)), 75),
]
for name, our_p, mkt_p in scenarios:
r_yes = kelly.size_bet(our_p, mkt_p, "buy_yes")
r_no = kelly.size_bet(our_p, mkt_p, "buy_no")
if r_yes.kelly_active and r_yes.size_usdc > r_no.size_usdc:
r = r_yes
side = "buy_yes"
elif r_no.kelly_active:
r = r_no
side = "buy_no"
else:
r = r_yes
side = "pass"
kelly_results.append({
"name": name,
"our_prob": round(our_p, 1),
"mkt_prob": round(mkt_p, 1),
"edge": round(r.edge, 3),
"side": side,
"active": bool(r.kelly_active),
"size": round(r.size_usdc, 2) if r.kelly_active else 0,
})
return jsonify({
"fetch_time": datetime.now().strftime("%H:%M:%S"),
"current": current_data,
"typhoon": typhoon_data,
"tomorrow": tomorrow_data,
"forecast": fc_series,
"signals": signals,
"kelly": kelly_results,
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/ml")
def api_ml():
"""Return ML model predictions."""
if not _ml_available:
return jsonify({"error": "ML not available"}), 503
try:
predictor = MLPredictor()
predictor.fetch_and_predict()
predictions = predictor._last_predictions or {}
# Per-model raw vs calibrated
models = {}
for target, model in predictor.ensemble.models.items():
if predictor._last_features is not None:
raw = float(model.predict_raw(predictor._last_features[1:2])[0]) if len(predictor._last_features) > 1 else 0.5
cal = float(model.predict_proba(predictor._last_features[1:2])[0]) if len(predictor._last_features) > 1 else 50.0
else:
raw, cal = 0.5, 50.0
models[target] = {
"description": model.target_def["description"],
"raw": round(raw * 100, 1),
"calibrated": round(cal, 1),
"method": model.calibrator.method,
}
# Typhoon
typhoon = {}
if predictor._last_typhoon:
for k in ["typhoon_T1", "typhoon_T3", "typhoon_T8",
"typhoon_T8_72h", "typhoon_T8_120h"]:
if k in predictor._last_typhoon:
typhoon[k] = round(predictor._last_typhoon[k], 1)
# Spatial
spatial = predictor._last_spatial or {}
return jsonify({
"fetch_time": datetime.now().strftime("%H:%M:%S"),
"models": models,
"typhoon": typhoon,
"spatial": spatial,
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/")
def index():
return render_template_string(HTML_TEMPLATE)
if __name__ == "__main__":
import os
debug = os.environ.get("FLASK_DEBUG", "").lower() in ("1", "true", "yes")
print("\n" + "=" * 56)
print(" HK Weather Prediction Market — Dev Dashboard")
print(f" Starting at: http://localhost:5000")
print("=" * 56 + "\n")
app.run(host="0.0.0.0", port=5000, debug=debug)