jtlevine Claude Opus 4.6 (1M context) commited on
Commit
af4d4e1
Β·
1 Parent(s): 8ea1ecd

Revert Vercel dashboard tracker, add HF Space HTML pipeline tracker

Browse files

The pipeline tracker belongs on the HF Space status page (where you
monitor runs), not on the Vercel frontend (where donors see the demo).

HF Space root page now shows:
- Live progress bar with step name while pipeline runs (auto-refreshes 5s)
- Last run result (status, zones, triggers, duration) when idle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

frontend/src/lib/api.ts CHANGED
@@ -313,29 +313,6 @@ export function usePipelineStats() {
313
  })
314
  }
315
 
316
- export interface PipelineStatus {
317
- running: boolean
318
- current_step: string | null
319
- current_step_index: number
320
- total_steps: number
321
- last_result: {
322
- run_id: string
323
- status: string
324
- zones_processed: number
325
- triggers_found: number
326
- duration_s: number
327
- } | null
328
- last_run: string | null
329
- }
330
-
331
- export function usePipelineStatus() {
332
- return useQuery<PipelineStatus>({
333
- queryKey: ['pipeline-status'],
334
- queryFn: () => fetchJson('/api/pipeline/status'),
335
- refetchInterval: 5000, // poll every 5s while running
336
- })
337
- }
338
-
339
  export function useEnrolled() {
340
  return useQuery<EnrolledResponse>({
341
  queryKey: ['enrolled'],
 
313
  })
314
  }
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  export function useEnrolled() {
317
  return useQuery<EnrolledResponse>({
318
  queryKey: ['enrolled'],
frontend/src/pages/Dashboard.tsx CHANGED
@@ -4,23 +4,13 @@ import { Satellite, Thermometer, SlidersHorizontal, ChevronDown, ChevronRight, A
4
  import MetricCard from '../components/MetricCard'
5
  import StatusBadge from '../components/StatusBadge'
6
  import { LoadingSpinner, ErrorState } from '../components/LoadingState'
7
- import { usePipelineStats, usePipelineRuns, useTriggers, usePipelineStatus } from '../lib/api'
8
  import { REGION } from '../regionConfig'
9
 
10
- const STEP_LABELS: Record<string, string> = {
11
- ingest: 'Collecting climate data',
12
- heal: 'Fixing data issues',
13
- downscale: 'Adjusting for urban heat',
14
- predict: 'Forecasting heat danger',
15
- explain: 'Writing alert messages',
16
- notify: 'Notifying workers',
17
- }
18
-
19
  export default function Dashboard() {
20
  const stats = usePipelineStats()
21
  const runs = usePipelineRuns()
22
  const triggers = useTriggers()
23
- const pipelineStatus = usePipelineStatus()
24
  const [showRuns, setShowRuns] = useState(false)
25
 
26
  if (stats.isLoading) return <LoadingSpinner />
@@ -43,36 +33,6 @@ export default function Dashboard() {
43
  <p className="page-caption">{REGION.heroCaption}</p>
44
  </div>
45
 
46
- {/* Pipeline running banner */}
47
- {pipelineStatus.data?.running && (
48
- <div className="mb-6 rounded-[10px] flex items-center gap-4" style={{
49
- background: 'linear-gradient(135deg, rgba(21,101,192,0.06) 0%, rgba(42,157,143,0.04) 100%)',
50
- border: '1px solid rgba(21,101,192,0.15)',
51
- padding: '14px 20px',
52
- }}>
53
- <div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{
54
- background: '#1565C0',
55
- boxShadow: '0 0 0 3px rgba(21,101,192,0.2)',
56
- animation: 'pulse 2s infinite',
57
- }} />
58
- <div className="flex-1">
59
- <div className="text-sm font-sans font-medium" style={{ color: '#1565C0' }}>
60
- Pipeline running β€” step {pipelineStatus.data.current_step_index}/{pipelineStatus.data.total_steps}:{' '}
61
- {STEP_LABELS[pipelineStatus.data.current_step ?? ''] ?? pipelineStatus.data.current_step}
62
- </div>
63
- <div className="mt-1.5 w-full h-1.5 rounded-full bg-warm-border overflow-hidden">
64
- <div
65
- className="h-full rounded-full transition-all duration-500"
66
- style={{
67
- width: `${(pipelineStatus.data.current_step_index / pipelineStatus.data.total_steps) * 100}%`,
68
- background: '#1565C0',
69
- }}
70
- />
71
- </div>
72
- </div>
73
- </div>
74
- )}
75
-
76
  {/* Danger banner β€” shows when there are active triggers */}
77
  {activeTriggers.length > 0 && (
78
  <div className="danger-banner">
 
4
  import MetricCard from '../components/MetricCard'
5
  import StatusBadge from '../components/StatusBadge'
6
  import { LoadingSpinner, ErrorState } from '../components/LoadingState'
7
+ import { usePipelineStats, usePipelineRuns, useTriggers } from '../lib/api'
8
  import { REGION } from '../regionConfig'
9
 
 
 
 
 
 
 
 
 
 
10
  export default function Dashboard() {
11
  const stats = usePipelineStats()
12
  const runs = usePipelineRuns()
13
  const triggers = useTriggers()
 
14
  const [showRuns, setShowRuns] = useState(false)
15
 
16
  if (stats.isLoading) return <LoadingSpinner />
 
33
  <p className="page-caption">{REGION.heroCaption}</p>
34
  </div>
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  {/* Danger banner β€” shows when there are active triggers */}
37
  {activeTriggers.length > 0 && (
38
  <div className="danger-banner">
frontend/tsconfig.tsbuildinfo CHANGED
@@ -1 +1 @@
1
- {"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/Layout.tsx","./src/components/LoadingState.tsx","./src/components/MetricCard.tsx","./src/components/Sidebar.tsx","./src/components/StatusBadge.tsx","./src/lib/api.ts","./src/lib/tour.ts","./src/pages/Dashboard.tsx","./src/pages/Notifications.tsx","./src/pages/Pipeline.tsx","./src/pages/ProgramDesigner.tsx","./src/pages/Zones.tsx"],"version":"5.6.3"}
 
1
+ {"root":["./src/App.tsx","./src/main.tsx","./src/regionConfig.ts","./src/vite-env.d.ts","./src/components/Layout.tsx","./src/components/LoadingState.tsx","./src/components/MetricCard.tsx","./src/components/Sidebar.tsx","./src/components/StatusBadge.tsx","./src/lib/api.ts","./src/lib/tour.ts","./src/pages/Dashboard.tsx","./src/pages/Notifications.tsx","./src/pages/Pipeline.tsx","./src/pages/ProgramDesigner.tsx","./src/pages/Zones.tsx"],"version":"5.6.3"}
src/api.py CHANGED
@@ -884,6 +884,44 @@ def _start_scheduler():
884
 
885
  # ── Status page (lightweight, no React build needed) ─────────────────────
886
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  @app.get("/", response_class=HTMLResponse)
888
  async def status_page():
889
  """Simple status dashboard for the HF Space operator."""
@@ -914,6 +952,7 @@ async def status_page():
914
  <head>
915
  <meta charset="utf-8">
916
  <meta name="viewport" content="width=device-width, initial-scale=1">
 
917
  <title>Heat Risk Engine β€” API Status</title>
918
  <style>
919
  * {{ margin: 0; padding: 0; box-sizing: border-box; }}
@@ -936,12 +975,20 @@ async def status_page():
936
  .link {{ color: #d4a019; text-decoration: none; font-weight: 600; }}
937
  .link:hover {{ text-decoration: underline; }}
938
  .footer {{ margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0dcd5; font-size: 0.75rem; color: #aaa; }}
 
 
 
 
 
 
939
  </style>
940
  </head>
941
  <body>
942
  <h1>Heat Risk Engine <span class="status">Running</span></h1>
943
  <p class="subtitle">API backend for the Climate Risk Engine β€” <a class="link" href="https://climate-risk-engine.vercel.app" target="_blank">Open Frontend</a></p>
944
 
 
 
945
  <div class="grid">
946
  <div class="card">
947
  <div class="label">Zones</div>
 
884
 
885
  # ── Status page (lightweight, no React build needed) ─────────────────────
886
 
887
+ _STEP_NAMES = {
888
+ "ingest": "Collecting climate data",
889
+ "heal": "Fixing data issues",
890
+ "downscale": "Adjusting for urban heat",
891
+ "predict": "Forecasting heat danger",
892
+ "explain": "Writing alert messages",
893
+ "notify": "Notifying workers",
894
+ }
895
+
896
+
897
+ def _pipeline_tracker_html() -> str:
898
+ """Generate HTML for the pipeline tracker banner."""
899
+ ps = _pipeline_status
900
+ if ps["running"] and ps.get("current_step"):
901
+ step = ps["current_step"]
902
+ idx = ps.get("current_step_index", 0)
903
+ total = ps.get("total_steps", 6)
904
+ label = _STEP_NAMES.get(step, step)
905
+ pct = int((idx / total) * 100) if total else 0
906
+ return f"""
907
+ <div class="pipeline-banner">
908
+ <div class="step-label">Pipeline running β€” step {idx}/{total}: {label}</div>
909
+ <div class="bar-bg"><div class="bar-fill" style="width:{pct}%"></div></div>
910
+ </div>"""
911
+ elif ps.get("last_result"):
912
+ r = ps["last_result"]
913
+ status = r.get("status", "unknown")
914
+ dur = r.get("duration_s", 0)
915
+ zones = r.get("zones_processed", 0)
916
+ triggers = r.get("triggers_found", 0)
917
+ color = "#2a9d8f" if status == "ok" else "#e63946"
918
+ return f"""
919
+ <div class="pipeline-done" style="border-color:{color}22">
920
+ <div class="label" style="color:{color}">Last run: {status.upper()} β€” {zones} zones, {triggers} triggers, {dur:.0f}s</div>
921
+ </div>"""
922
+ return ""
923
+
924
+
925
  @app.get("/", response_class=HTMLResponse)
926
  async def status_page():
927
  """Simple status dashboard for the HF Space operator."""
 
952
  <head>
953
  <meta charset="utf-8">
954
  <meta name="viewport" content="width=device-width, initial-scale=1">
955
+ <meta http-equiv="refresh" content="5">
956
  <title>Heat Risk Engine β€” API Status</title>
957
  <style>
958
  * {{ margin: 0; padding: 0; box-sizing: border-box; }}
 
975
  .link {{ color: #d4a019; text-decoration: none; font-weight: 600; }}
976
  .link:hover {{ text-decoration: underline; }}
977
  .footer {{ margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0dcd5; font-size: 0.75rem; color: #aaa; }}
978
+ .pipeline-banner {{ background: linear-gradient(135deg, rgba(21,101,192,0.06), rgba(42,157,143,0.04)); border: 1px solid rgba(21,101,192,0.15); border-radius: 8px; padding: 14px 16px; margin-bottom: 20px; }}
979
+ .pipeline-banner .step-label {{ font-size: 0.85rem; font-weight: 600; color: #1565C0; }}
980
+ .pipeline-banner .bar-bg {{ width: 100%; height: 6px; background: #e0dcd5; border-radius: 3px; margin-top: 8px; overflow: hidden; }}
981
+ .pipeline-banner .bar-fill {{ height: 100%; background: #1565C0; border-radius: 3px; transition: width 0.5s; }}
982
+ .pipeline-done {{ background: rgba(42,157,143,0.06); border: 1px solid rgba(42,157,143,0.15); border-radius: 8px; padding: 14px 16px; margin-bottom: 20px; }}
983
+ .pipeline-done .label {{ font-size: 0.85rem; font-weight: 600; color: #2a9d8f; }}
984
  </style>
985
  </head>
986
  <body>
987
  <h1>Heat Risk Engine <span class="status">Running</span></h1>
988
  <p class="subtitle">API backend for the Climate Risk Engine β€” <a class="link" href="https://climate-risk-engine.vercel.app" target="_blank">Open Frontend</a></p>
989
 
990
+ {_pipeline_tracker_html()}
991
+
992
  <div class="grid">
993
  <div class="card">
994
  <div class="label">Zones</div>
tests/eval_results/heat_predictor_eval.json CHANGED
@@ -1,33 +1,33 @@
1
  {
2
  "NBO-KIB": {
3
  "city": "Nairobi",
4
- "auroc": 0.723,
5
- "precision": 0.447,
6
- "recall": 0.765,
7
  "n_samples": 328,
8
  "positive_rate": 0.351
9
  },
10
  "DAR-JAN": {
11
  "city": "Dar es Salaam",
12
- "auroc": 0.155,
13
- "precision": 0.027,
14
- "recall": 0.017,
15
  "n_samples": 328,
16
  "positive_rate": 0.351
17
  },
18
  "KLA-BWA": {
19
  "city": "Kampala",
20
- "auroc": 0.335,
21
- "precision": 0.254,
22
- "recall": 0.383,
23
  "n_samples": 328,
24
  "positive_rate": 0.351
25
  },
26
  "KGL-NYA": {
27
  "city": "Kigali",
28
- "auroc": 0.674,
29
- "precision": 0.41,
30
- "recall": 0.713,
31
  "n_samples": 328,
32
  "positive_rate": 0.351
33
  }
 
1
  {
2
  "NBO-KIB": {
3
  "city": "Nairobi",
4
+ "auroc": 0.868,
5
+ "precision": 0.808,
6
+ "recall": 0.548,
7
  "n_samples": 328,
8
  "positive_rate": 0.351
9
  },
10
  "DAR-JAN": {
11
  "city": "Dar es Salaam",
12
+ "auroc": 0.138,
13
+ "precision": 0.029,
14
+ "recall": 0.009,
15
  "n_samples": 328,
16
  "positive_rate": 0.351
17
  },
18
  "KLA-BWA": {
19
  "city": "Kampala",
20
+ "auroc": 0.322,
21
+ "precision": 0.303,
22
+ "recall": 0.235,
23
  "n_samples": 328,
24
  "positive_rate": 0.351
25
  },
26
  "KGL-NYA": {
27
  "city": "Kigali",
28
+ "auroc": 0.795,
29
+ "precision": 0.726,
30
+ "recall": 0.6,
31
  "n_samples": 328,
32
  "positive_rate": 0.351
33
  }