#!/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

HK Weather Prediction Market

WeatherNext + HKO + Polymarket Strategy

● Live HKO + Open-Meteo

Current Conditions HKO

Loading...

Tomorrow

Temp Max --
Temp Min --
Rain Probability --
Rain Total --
Wind Max --
Gusts Max --

5-Day Temperature Forecast

Open-Meteo + HKO comparison

Rain Probability

Wind Speed Forecast

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("/simple") def simple(): """Simple unstyled dashboard that always works.""" return render_template_string(""" HK Weather — Simple

HK Weather Prediction Market

Current Conditions (HKO)

Loading...

Tomorrow Forecast

Loading...

ML Model Predictions

Loading...

5-Day Forecast

Loading...

Typhoon

Loading...

Kelly Sizing

Loading...
""") @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)