feat: quake multi-line wire format + GUI panel wired to real adapter_config keys

A) _render() now emits multi-line format matching Fire/Roads style:
   emoji prefix M{mag} — place / Depth · coords / TSUNAMI WARNING
B) Environment.tsx usgs_quake panel replaced — dead min_magnitude/bbox
   controls removed, wired to real adapter_config keys: global_mag_floor,
   regional_mag_floor, regional_radius_mi, escalate_mag_floor,
   broadcast_pager_alerts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Johnson (via Claude) 2026-06-09 03:38:46 +00:00
commit fe6589e0e5
4 changed files with 209 additions and 153 deletions

View file

@ -23,7 +23,7 @@ interface EnvConfig {
fires: { enabled: boolean; tick_seconds: number; state: string; feed_source?: FeedSource }
avalanche: { enabled: boolean; tick_seconds: number; center_ids: string[]; season_months: number[]; feed_source?: FeedSource }
usgs: { enabled: boolean; tick_seconds: number; sites: string[]; feed_source?: FeedSource }
usgs_quake: { enabled: boolean; tick_seconds: number; feed_url: string; min_magnitude: number; bbox: number[]; region: string; feed_source?: FeedSource }
usgs_quake: { enabled: boolean; tick_seconds: number; feed_url: string; global_mag_floor: number; regional_mag_floor: number; regional_radius_mi: number; escalate_mag_floor: number; broadcast_pager_alerts: string[]; region: string; feed_source?: FeedSource }
traffic: { enabled: boolean; tick_seconds: number; api_key: string; corridors: { name: string; lat: number; lon: number }[]; feed_source?: FeedSource }
roads511: { enabled: boolean; tick_seconds: number; api_key: string; base_url: string; endpoints: string[]; bbox: number[]; feed_source?: FeedSource }
wzdx: { enabled: boolean; tick_seconds: number; api_key: string; base_url: string; endpoints: string[]; bbox: number[]; feed_source?: FeedSource }
@ -665,17 +665,63 @@ const save = async () => {
<NumberInput label="Tick Seconds" value={env.usgs.tick_seconds} onChange={(v) => up({ usgs: { ...env.usgs, tick_seconds: v } })} min={900} helper="Minimum 15 min (900s). tick_seconds is the native-mode poll interval; ignored when this adapter is set to feed_source=central." />
<ListInput label="Site IDs" value={env.usgs.sites} onChange={(v) => up({ usgs: { ...env.usgs, sites: v } })} helper="USGS gauge site numbers" infoLink="https://waterdata.usgs.gov/nwis" />
</>)
case 'usgs_quake': return (<>
<NumberInput label="Tick Seconds" value={env.usgs_quake.tick_seconds} onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, tick_seconds: v } })} min={60} />
<NumberInput label="Min Magnitude" value={env.usgs_quake.min_magnitude} onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, min_magnitude: v } })} step={0.1} min={0} />
<TextInput label="Region Tag" value={env.usgs_quake.region} onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, region: v } })} />
<div className="grid grid-cols-4 gap-2">
{(['West', 'South', 'East', 'North'] as const).map((lbl, i) => (
<NumberInput key={lbl} label={lbl} value={env.usgs_quake.bbox?.[i] ?? 0} onChange={(v) => { const b = [...(env.usgs_quake.bbox || [0, 0, 0, 0])]; b[i] = v; up({ usgs_quake: { ...env.usgs_quake, bbox: b } }) }} step={0.01} />
))}
case 'usgs_quake': return (
<div className="space-y-6">
{env.usgs_quake.feed_source !== 'central' && (
<div className="grid grid-cols-2 gap-4">
<NumberInput label="Tick Seconds" value={env.usgs_quake.tick_seconds}
onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, tick_seconds: v } })}
min={60} />
<TextInput label="Region Tag" value={env.usgs_quake.region}
onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, region: v } })} />
</div>
)}
<div>
<div className="text-xs font-medium text-slate-400 uppercase tracking-wider mb-3">
Magnitude Thresholds
</div>
<div className="grid grid-cols-2 gap-4">
<NumberInput label="Global Floor" value={env.usgs_quake.global_mag_floor}
onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, global_mag_floor: v } })}
step={0.1} min={0} helper="Broadcast anywhere at or above this magnitude" />
<NumberInput label="Regional Floor" value={env.usgs_quake.regional_mag_floor}
onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, regional_mag_floor: v } })}
step={0.1} min={0} helper="Reduced floor within regional radius" />
<NumberInput label="Regional Radius (mi)" value={env.usgs_quake.regional_radius_mi}
onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, regional_radius_mi: v } })}
min={50} helper="Radius around region centroid for reduced floor" />
<NumberInput label="Escalation Floor" value={env.usgs_quake.escalate_mag_floor}
onChange={(v) => up({ usgs_quake: { ...env.usgs_quake, escalate_mag_floor: v } })}
step={0.1} min={0} helper="Magnitude at which broadcast uses warning emoji" />
</div>
</div>
<div>
<div className="text-xs font-medium text-slate-400 uppercase tracking-wider mb-3">
PAGER Alert Levels
</div>
<div className="text-xs text-slate-500 mb-2">
Broadcast at any magnitude when USGS PAGER alert reaches these levels
</div>
<div className="flex gap-6">
{(['green','yellow','orange','red'] as const).map((level) => (
<label key={level} className="flex items-center gap-2 cursor-pointer">
<input type="checkbox"
checked={env.usgs_quake.broadcast_pager_alerts.includes(level)}
onChange={(e) => {
const cur = env.usgs_quake.broadcast_pager_alerts
up({ usgs_quake: { ...env.usgs_quake,
broadcast_pager_alerts: e.target.checked
? [...cur, level]
: cur.filter((l) => l !== level) }})
}}
className="w-4 h-4 rounded accent-blue-500" />
<span className="text-sm text-slate-300 capitalize">{level}</span>
</label>
))}
</div>
</div>
</div>
<div className="text-xs text-slate-500">Bounding box [W,S,E,N] geographic filter</div>
</>)
)
case 'traffic': return (<>
<TextInput label="API Key" value={env.traffic.api_key} onChange={(v) => up({ traffic: { ...env.traffic, api_key: v } })} type="password" helper="developer.tomtom.com" />
<NumberInput label="Tick Seconds" value={env.traffic.tick_seconds} onChange={(v) => up({ traffic: { ...env.traffic, tick_seconds: v } })} min={60} />

View file

@ -6,8 +6,10 @@ Broadcast gate (any of these triggers):
(c) tsunami_warning at any magnitude
(d) PAGER alert level in {orange, red}
Wire format:
{emoji} Magnitude {mag:.1f} earthquake {place_string}, {depth}km depth, @ {lat:.3f},{lon:.3f}
Wire format (multi-line, matches Fire/Roads style):
Line 1: {emoji} {prefix} M{mag:.1f} {place_string}
Line 2: Depth: {depth} km · @ {lat:.3f}, {lon:.3f}
Line 3: 🚨 TSUNAMI WARNING only when tsunami flag is set
Emoji:
Routine -> 🌐
@ -159,32 +161,40 @@ def handle_quake(envelope: dict, subject: str,
1 if tsunami else 0, now, None),
)
wire = _render(mag=mag, place=place, depth_km=depth_km, lat=lat, lon=lon,
tsunami=tsunami)
tsunami=tsunami, is_update=False)
_attach_commit(data, event_id=event_id, event_log_row_id=log_id)
return wire
if row["last_broadcast_at"] is None:
wire = _render(mag=mag, place=place, depth_km=depth_km, lat=lat, lon=lon,
tsunami=tsunami)
tsunami=tsunami, is_update=False)
_attach_commit(data, event_id=event_id, event_log_row_id=log_id)
return wire
return None
def _render(*, mag, place, depth_km, lat, lon, tsunami) -> str:
def _render(*, mag, place, depth_km, lat, lon, tsunami, is_update=False) -> str:
emoji = _emoji_for(mag, tsunami)
mag_str = f"{mag:.1f}" if isinstance(mag, (int, float)) else "?"
place_str = place if place else "(location unknown)"
place_str = place if place else "unknown location"
prefix = "Update:" if is_update else "New:"
# Line 1: prefix + magnitude + place
line1 = f"{emoji} {prefix} M{mag_str} \u2014 {place_str}"
# Line 2: depth + coords
parts = []
if isinstance(depth_km, (int, float)):
depth_seg = f", {int(round(depth_km))}km depth"
else:
depth_seg = ""
coords = ""
parts.append(f"Depth: {int(round(depth_km))} km")
if isinstance(lat, (int, float)) and isinstance(lon, (int, float)):
coords = f", @ {lat:.3f},{lon:.3f}"
tsunami_seg = " -- TSUNAMI WARNING" if tsunami else ""
return f"{emoji} Magnitude {mag_str} earthquake {place_str}{depth_seg}{coords}{tsunami_seg}"
parts.append(f"@ {lat:.3f}, {lon:.3f}")
line2 = " \u00b7 ".join(parts) if parts else None
# Line 3: tsunami warning (only when present)
line3 = "\U0001f6a8 TSUNAMI WARNING" if tsunami else None
return "\n".join(l for l in [line1, line2, line3] if l)
def _attach_commit(data: Optional[dict], *, event_id: str,

View file

@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-De10FgTg.js"></script>
<script type="module" crossorigin src="/assets/index-CkIzxSuY.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dp9XCfH-.css">
</head>
<body>