Spaces:
Sleeping
Sleeping
Download ui/_stubs.py from lucashudsn/wavereader: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/spaces/lucashudsn/wavereader/resolve/main/ui/_stubs.py
- Command line
-
hf download hf://spaces/lucashudsn/wavereader/ui/_stubs.py
-
curl -L -o _stubs.py https://huggingface.co/spaces/lucashudsn/wavereader/resolve/main/ui/_stubs.py
13 kB
| """Minimal local stand-ins for Worker A/C modules (NOT the real core). | |
| Workers A/C own ``wavereader/*`` on parallel branches; those modules do not | |
| exist on this branch yet. This module provides deterministic fakes with the | |
| same *shapes* the real tools will return, so the UI builds and renders | |
| offline today. | |
| MERGE REWIRING (orchestrator): ``ui/_compat.py`` already tries the real | |
| ``wavereader.*`` imports first — once Worker A/C merge, the stubs stop being | |
| used with no UI changes. Delete this module only when ``wavereader/tools.py`` | |
| + ``wavereader/climate.py`` + ``wavereader/seafloor.py`` are all merged. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import math | |
| from datetime import date, timedelta | |
| SKILL_ORDER = ["beginner", "intermediate", "advanced", "expert", "pro-only"] | |
| STUB_BREAKS: list[dict] = [ | |
| { | |
| "id": "stub-bells-beach", | |
| "name": "Bells Beach", | |
| "state": "Victoria", | |
| "region": "Surf Coast", | |
| "description": "Famous right-hand reef break (stub record for UI development).", | |
| "skillLevel": "intermediate", | |
| "breakType": "reef", | |
| "peakType": "right", | |
| "location": {"country": "Australia", "state": "Victoria", "region": "Surf Coast", | |
| "coordinates": {"lat": -38.3667, "lng": 144.2833}}, | |
| "idealSwell": {"direction": ["SW", "S"], "sizeRangeFt": {"min": 3, "max": 8}}, | |
| "idealWind": {"direction": ["N", "NW"], "type": "offshore"}, | |
| "idealTide": {"stage": ["mid", "high"]}, | |
| "bestSeason": ["autumn", "winter"], | |
| "hazards": ["rocks", "crowd"], | |
| "crowdFactor": "high", | |
| }, | |
| { | |
| "id": "stub-snapper-rocks", | |
| "name": "Snapper Rocks", | |
| "state": "Queensland", | |
| "region": "Gold Coast", | |
| "description": "Superbank sand point, long right (stub record).", | |
| "skillLevel": "advanced", | |
| "breakType": "sand", | |
| "peakType": "right", | |
| "location": {"country": "Australia", "state": "Queensland", "region": "Gold Coast", | |
| "coordinates": {"lat": -28.1656, "lng": 153.5500}}, | |
| "idealSwell": {"direction": ["E", "SE"], "sizeRangeFt": {"min": 2, "max": 6}}, | |
| "idealWind": {"direction": ["SW", "S"], "type": "offshore"}, | |
| "idealTide": {"stage": ["mid"]}, | |
| "bestSeason": ["autumn", "winter"], | |
| "hazards": ["crowd", "rocks"], | |
| "crowdFactor": "high", | |
| }, | |
| { | |
| "id": "stub-noosa-heads", | |
| "name": "Noosa Heads", | |
| "state": "Queensland", | |
| "region": "Sunshine Coast", | |
| "description": "Gentle right points, beginner friendly (stub record).", | |
| "skillLevel": "beginner", | |
| "breakType": "point", | |
| "peakType": "right", | |
| "location": {"country": "Australia", "state": "Queensland", "region": "Sunshine Coast", | |
| "coordinates": {"lat": -26.3833, "lng": 153.1000}}, | |
| "idealSwell": {"direction": ["N", "NE"], "sizeRangeFt": {"min": 1, "max": 4}}, | |
| "idealWind": {"direction": ["SW"], "type": "offshore"}, | |
| "idealTide": {"stage": ["mid", "high"]}, | |
| "bestSeason": ["summer", "autumn"], | |
| "hazards": ["crowd"], | |
| "crowdFactor": "high", | |
| }, | |
| { | |
| "id": "stub-the-pass", | |
| "name": "The Pass", | |
| "state": "New South Wales", | |
| "region": "Byron", | |
| "description": "Mellow sand point at Byron Bay (stub record).", | |
| "skillLevel": "beginner", | |
| "breakType": "sand", | |
| "peakType": "right", | |
| "location": {"country": "Australia", "state": "New South Wales", "region": "Byron", | |
| "coordinates": {"lat": -28.6333, "lng": 153.6333}}, | |
| "idealSwell": {"direction": ["E", "NE"], "sizeRangeFt": {"min": 1, "max": 4}}, | |
| "idealWind": {"direction": ["SW", "W"], "type": "offshore"}, | |
| "idealTide": {"stage": ["mid"]}, | |
| "bestSeason": ["autumn"], | |
| "hazards": ["crowd"], | |
| "crowdFactor": "high", | |
| }, | |
| { | |
| "id": "stub-margaret-river", | |
| "name": "Margaret River Main Break", | |
| "state": "Western Australia", | |
| "region": "South West", | |
| "description": "Heavy reef, expert only (stub record).", | |
| "skillLevel": "expert", | |
| "breakType": "reef", | |
| "peakType": "a-frame", | |
| "location": {"country": "Australia", "state": "Western Australia", | |
| "region": "South West", | |
| "coordinates": {"lat": -33.9500, "lng": 114.9833}}, | |
| "idealSwell": {"direction": ["SW", "W"], "sizeRangeFt": {"min": 4, "max": 12}}, | |
| "idealWind": {"direction": ["E", "NE"], "type": "offshore"}, | |
| "idealTide": {"stage": ["mid"]}, | |
| "bestSeason": ["winter"], | |
| "hazards": ["rocks", "shallow reef", "sharks"], | |
| "crowdFactor": "medium", | |
| }, | |
| { | |
| "id": "stub-bondi", | |
| "name": "Bondi Beach", | |
| "state": "New South Wales", | |
| "region": "Sydney", | |
| "description": "City beach break, peaks up and down the beach (stub record).", | |
| "skillLevel": "beginner", | |
| "breakType": "beach", | |
| "peakType": "a-frame", | |
| "location": {"country": "Australia", "state": "New South Wales", "region": "Sydney", | |
| "coordinates": {"lat": -33.8915, "lng": 151.2767}}, | |
| "idealSwell": {"direction": ["S", "SE"], "sizeRangeFt": {"min": 1, "max": 4}}, | |
| "idealWind": {"direction": ["W", "NW"], "type": "offshore"}, | |
| "idealTide": {"stage": ["low", "mid"]}, | |
| "bestSeason": ["summer", "autumn"], | |
| "hazards": ["crowd", "rips"], | |
| "crowdFactor": "high", | |
| }, | |
| ] | |
| _DIRECTIONS_16 = [ | |
| "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", | |
| "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", | |
| ] | |
| _MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", | |
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] | |
| def _rng01(key: str) -> float: | |
| h = hashlib.md5(key.encode("utf-8")).hexdigest() | |
| return int(h[:8], 16) / 0xFFFFFFFF | |
| def _ideal_mid(break_: dict) -> float: | |
| size = ((break_ or {}).get("idealSwell") or {}).get("sizeRangeFt") or {} | |
| try: | |
| return (float(size.get("min", 1)) + float(size.get("max", 4))) / 2.0 | |
| except (TypeError, ValueError): | |
| return 2.5 | |
| def stub_scored_hours(break_: dict, skill: str = "intermediate", | |
| days: int = 3) -> list[dict]: | |
| """Deterministic fake scored hours (06:00–17:00 local, 12/day). | |
| Shape matches the real ``score_week`` hour rows: ``time, score, | |
| wave_height_m, wave_period_s, wind_speed_kt, wind_direction_deg``. | |
| """ | |
| name = (break_ or {}).get("name", "?") | |
| mid = _ideal_mid(break_) | |
| hours: list[dict] = [] | |
| today = date(2026, 9, 9) | |
| for d in range(max(1, min(7, int(days)))): | |
| day = today + timedelta(days=d + 1) | |
| for h in range(6, 18): | |
| key = f"{name}|{day.isoformat()}|{h}" | |
| swell = mid + 1.1 * math.sin(d * 1.3 + h * 0.35) + (_rng01(key + "h") - 0.5) * 1.6 | |
| swell = max(0.3, round(swell, 1)) | |
| period = round(6 + 7 * _rng01(key + "p") + 0.8 * math.sin(h), 1) | |
| wind = round(max(0.0, 8 + 14 * _rng01(key + "w") - 6 * math.sin(h * 0.5)), 1) | |
| wdir = int(_rng01(key + "d") * 360) % 360 | |
| # Plausible v1-style score: size closeness, wind penalty, period bonus. | |
| size_score = max(0.0, 10 - abs(swell - mid) * 2.2) | |
| wind_score = max(0.0, 10 - max(0.0, wind - 5) * 0.7) | |
| period_score = min(10.0, max(0.0, (period - 4) * 1.1)) | |
| score = round(min(10.0, max(0.0, 0.4 * size_score + 0.35 * wind_score + 0.25 * period_score)), 1) | |
| hours.append( | |
| { | |
| "time": f"{day.isoformat()}T{h:02d}:00", | |
| "score": score, | |
| "wave_height_m": swell, | |
| "wave_period_s": period, | |
| "wind_speed_kt": wind, | |
| "wind_direction_deg": float(wdir), | |
| } | |
| ) | |
| return hours | |
| def _best(hours: list[dict]) -> dict | None: | |
| return max(hours, key=lambda r: r.get("score", 0)) if hours else None | |
| def _daily_best(hours: list[dict]) -> list[dict]: | |
| best_by_date: dict[str, dict] = {} | |
| for row in hours: | |
| day = str(row.get("time", ""))[:10] | |
| if day and (day not in best_by_date or row.get("score", 0) > best_by_date[day].get("score", 0)): | |
| best_by_date[day] = row | |
| return [ | |
| {"date": d, "time": r.get("time"), "score": r.get("score"), | |
| "wave_height_m": r.get("wave_height_m"), "wave_period_s": r.get("wave_period_s"), | |
| "wind_speed_kt": r.get("wind_speed_kt"), "wind_direction_deg": r.get("wind_direction_deg")} | |
| for d, r in sorted(best_by_date.items()) | |
| ] | |
| def find_stub(name: str) -> dict | None: | |
| """Find a stub break by name (case-insensitive).""" | |
| want = (name or "").strip().lower() | |
| for b in STUB_BREAKS: | |
| if b.get("name", "").strip().lower() == want: | |
| return b | |
| return None | |
| def score_week(spot_name: str, region: str = "", | |
| skill: str = "intermediate") -> dict: | |
| """Fake ``score_week`` tool payload: spot/skill/scored/daily/best.""" | |
| break_ = find_stub(spot_name) or dict(STUB_BREAKS[0]) | |
| if region: | |
| break_ = dict(break_, region=region) | |
| hours = stub_scored_hours(break_, skill=skill, days=3) | |
| return { | |
| "spot": {"name": break_.get("name"), "region": break_.get("region")}, | |
| "skill": skill, | |
| "scored": hours, | |
| "daily": _daily_best(hours), | |
| "best": _best(hours), | |
| } | |
| def rank_region_week(region: str, skill: str = "intermediate") -> dict: | |
| """Fake ``rank_region_week`` payload: ``{"region", "rank": [rows]}``.""" | |
| cands = [b for b in STUB_BREAKS if b.get("region", "").lower() == region.lower()] or STUB_BREAKS[:4] | |
| rows = [] | |
| for b in cands: | |
| hours = stub_scored_hours(b, skill=skill, days=2) | |
| best = _best(hours) or {} | |
| rows.append( | |
| { | |
| "name": b.get("name"), | |
| "region": b.get("region"), | |
| "best_score": best.get("score"), | |
| "best_time": best.get("time"), | |
| } | |
| ) | |
| rows.sort(key=lambda r: r.get("best_score") or 0, reverse=True) | |
| return {"region": region, "skill": skill, "rank": rows} | |
| def explain_score(spot_name: str, skill: str = "intermediate", time: str = "") -> dict: | |
| """Fake ``explain_score`` payload: score + v1-style component breakdown.""" | |
| payload = score_week(spot_name, skill=skill) | |
| hours = payload["scored"] | |
| row = next((h for h in hours if h.get("time") == time), None) or _best(hours) or {} | |
| score = float(row.get("score") or 0) | |
| return { | |
| "spot": payload["spot"], | |
| "skill": skill, | |
| "time": row.get("time"), | |
| "score": score, | |
| "components": { | |
| "size": round(min(10.0, score + 0.6), 1), | |
| "direction": round(min(10.0, score + 0.2), 1), | |
| "wind": round(max(0.0, score - 0.8), 1), | |
| "period": round(min(10.0, score + 0.4), 1), | |
| }, | |
| "note": "stub breakdown (weights 30/20/30/20) — real engine lands with Worker A.", | |
| } | |
| def climate_profile(break_: dict) -> dict: | |
| """Fake 5-year climate profile: 16-bin rose (%), best months, medians.""" | |
| name = (break_ or {}).get("name", "?") | |
| raw = [_rng01(f"{name}|rose|{d}") + 0.15 for d in _DIRECTIONS_16] | |
| total = sum(raw) | |
| rose = {d: round(v / total * 100, 1) for d, v in zip(_DIRECTIONS_16, raw)} | |
| months = sorted(_MONTHS, key=lambda m: _rng01(f"{name}|month|{m}"), reverse=True)[:3] | |
| return { | |
| "rose": rose, | |
| "best_months": months, | |
| "median_height_m": round(0.6 + 2.4 * _rng01(f"{name}|h"), 1), | |
| "median_period_s": round(6 + 6 * _rng01(f"{name}|p"), 1), | |
| } | |
| def seafloor_grid(break_: dict, radius_km: float = 1.2, n: int = 9) -> dict: | |
| """Fake bathymetry grid: depth increasing seaward + noise.""" | |
| coords = ((break_ or {}).get("location") or {}).get("coordinates") or {} | |
| lat = float(coords.get("lat", -33.9)) | |
| lng = float(coords.get("lng", 151.3)) | |
| name = (break_ or {}).get("name", "?") | |
| half_lat = radius_km / 111.0 | |
| half_lng = radius_km / max(20.0, 111.0 * math.cos(math.radians(lat))) | |
| lats = [round(lat + ((i / (n - 1)) * 2 - 1) * half_lat, 5) for i in range(n)] | |
| lngs = [round(lng + ((j / (n - 1)) * 2 - 1) * half_lng, 5) for j in range(n)] | |
| elev = [] | |
| for i in range(n): | |
| for j in range(n): | |
| # Deeper to the east (seaward for east-coast stubs) + dip + noise. | |
| e = -2 - j * 2.2 - abs(i - n // 2) * 0.7 + (_rng01(f"{name}|sf|{i},{j}") - 0.5) * 3 | |
| if j == 0 and i in (0, n - 1): | |
| e = abs(e) * 0.4 # land corners | |
| elev.append(round(e, 1)) | |
| return {"lats": lats, "lngs": lngs, "elev": elev, "n": n, | |
| "radius_km": radius_km, "dataset": "stub-gebco", | |
| "center": {"lat": lat, "lng": lng}} | |