| """Quick end-to-end sanity check. |
| |
| Exercises every public route once and prints a summary. Catches: |
| - 404/500s on routes |
| - missing static assets |
| - broken /api/stream or /api/compare SSE |
| - missing register data |
| - hallucination drops > N |
| |
| Run while the server is up: |
| python scripts/dry_run.py |
| """ |
| from __future__ import annotations |
|
|
| import json |
| import sys |
| import time |
|
|
| import httpx |
|
|
| BASE = "http://127.0.0.1:8765" |
|
|
|
|
| def check(label: str, fn): |
| t0 = time.time() |
| try: |
| ok, detail = fn() |
| elapsed = time.time() - t0 |
| marker = "✓" if ok else "✗" |
| print(f" {marker} {label:42s} ({elapsed:5.2f}s) {detail}") |
| return ok |
| except Exception as e: |
| elapsed = time.time() - t0 |
| print(f" ✗ {label:42s} ({elapsed:5.2f}s) EXCEPTION: {type(e).__name__}: {e}") |
| return False |
|
|
|
|
| def get_status(path: str) -> tuple[bool, str]: |
| r = httpx.get(BASE + path, timeout=10) |
| return r.status_code == 200, f"HTTP {r.status_code} ({len(r.content)} bytes)" |
|
|
|
|
| def stream_one(query: str) -> tuple[bool, str]: |
| with httpx.stream("GET", BASE + f"/api/stream?q={query}", timeout=120) as r: |
| if r.status_code != 200: |
| return False, f"HTTP {r.status_code}" |
| steps = 0; final = None |
| for line in r.iter_lines(): |
| if line.startswith("data: "): |
| d = json.loads(line[6:]) |
| if d.get("kind") == "step": steps += 1 |
| elif d.get("kind") == "final": final = d |
| if not final: |
| return False, f"no final event (steps={steps})" |
| dropped = len((final.get("audit") or {}).get("dropped") or []) |
| en = final.get("energy") or {} |
| return True, (f"steps={steps}, dropped={dropped}, " |
| f"energy={en.get('local_mwh','?')} mWh local") |
|
|
|
|
| def compare_one(a: str, b: str) -> tuple[bool, str]: |
| with httpx.stream("GET", BASE + f"/api/compare?a={a}&b={b}", timeout=120) as r: |
| if r.status_code != 200: |
| return False, f"HTTP {r.status_code}" |
| finals = {} |
| steps = 0 |
| for line in r.iter_lines(): |
| if line.startswith("data: "): |
| d = json.loads(line[6:]) |
| if d.get("kind") == "step": steps += 1 |
| elif d.get("kind") == "final": finals[d.get("side")] = d |
| if "a" not in finals or "b" not in finals: |
| return False, f"missing final (got {list(finals)})" |
| return True, f"both sides done; steps={steps}" |
|
|
|
|
| def register_check(asset_class: str) -> tuple[bool, str]: |
| r = httpx.get(BASE + f"/api/register/{asset_class}", timeout=10) |
| if r.status_code == 503: |
| return False, "register not built" |
| if r.status_code != 200: |
| return False, f"HTTP {r.status_code}" |
| data = r.json() |
| rows = data.get("rows", []) |
| tiers = {1: 0, 2: 0, 3: 0} |
| for r_ in rows: |
| tiers[r_.get("tier", 0)] = tiers.get(r_.get("tier", 0), 0) + 1 |
| return True, f"{len(rows)} rows · tier1={tiers.get(1,0)} t2={tiers.get(2,0)} t3={tiers.get(3,0)}" |
|
|
|
|
| def main(): |
| print(f"=== Riprap dry-run vs {BASE} ===\n") |
|
|
| print("[Pages]") |
| check("/", lambda: get_status("/")) |
| check("/compare", lambda: get_status("/compare")) |
| check("/register/schools", lambda: get_status("/register/schools")) |
| check("/register/nycha", lambda: get_status("/register/nycha")) |
| check("/static/style.css", lambda: get_status("/static/style.css")) |
| check("/static/app.js", lambda: get_status("/static/app.js")) |
| check("/static/compare.js", lambda: get_status("/static/compare.js")) |
| check("/static/register.js",lambda: get_status("/static/register.js")) |
| fontf = "/static/vendor/nyco/fonts/IBM-Plex-Sans/IBMPlexSans-Regular.woff2" |
| check(fontf, lambda: get_status(fontf)) |
|
|
| print("\n[API: layer endpoints]") |
| check("/api/layers/sandy", lambda: get_status("/api/layers/sandy?lat=40.59&lon=-73.77&r=1500")) |
| check("/api/layers/dep_extreme_2080", |
| lambda: get_status("/api/layers/dep_extreme_2080?lat=40.59&lon=-73.77&r=1500")) |
| check("/api/floodnet_near", lambda: get_status("/api/floodnet_near?lat=40.59&lon=-73.77&r=1000")) |
|
|
| print("\n[API: register endpoints]") |
| check("/api/register/schools", lambda: register_check("schools")) |
| check("/api/register/nycha", lambda: register_check("nycha")) |
|
|
| print("\n[Streams]") |
| check("stream · 180 Beach 35 St", |
| lambda: stream_one("180 Beach 35 St, Queens")) |
| check("stream · Empire State (cleaner case)", |
| lambda: stream_one("350 5 Avenue, Manhattan")) |
| check("compare · Hollis vs Empire State", |
| lambda: compare_one("153-09 90 Avenue Jamaica Queens", |
| "350 5 Avenue Manhattan")) |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|