mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 01:14:45 +02:00
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:
parent
951fddf079
commit
fe6589e0e5
4 changed files with 209 additions and 153 deletions
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue