ginnyxxxxxxx commited on
Commit
883ef51
Β·
1 Parent(s): 626908f
Files changed (1) hide show
  1. app.py +69 -40
app.py CHANGED
@@ -285,75 +285,103 @@ def _dots():
285
 
286
 
287
  def render_chain(s1_text, s2_text, s3_text, status="done"):
288
- # status: idle | running1 | running2 | running3 | done
289
  s1_active = status in ("running1", "running2", "running3", "done")
290
  s2_active = status in ("running2", "running3", "done")
291
  s3_active = status in ("running3", "done")
292
 
293
- # ── Stage 1 ───────────────────────────────────────────────────────────────
294
  if status == "running1":
295
  s1_content = f'<div class="thinking">Extracting features {_dots()}</div>'
296
  elif s1_text:
297
  tags = []
298
  for line in s1_text.splitlines():
299
  line = line.strip().lstrip("-").strip()
300
- if line and len(line) < 65 and not line.endswith(":"):
 
301
  tags.append(line)
302
- if len(tags) >= 9:
303
  break
304
- s1_content = '<div class="tag-row">' + \
305
- "".join(f'<span class="tag">{t}</span>' for t in tags[:9]) + \
306
- '</div>'
307
  else:
308
- s1_content = '<div class="empty-hint">Press β–Ά Run HiCoTraj to start</div>'
309
-
310
- # ── Stage 2 ───────────────────────────────────────────────────────────────
311
- KEYS = ["SCHEDULE", "ECONOMIC", "SOCIAL", "LIFESTYLE", "STABILITY"]
 
 
 
 
 
 
312
  if status == "running2":
313
  s2_content = f'<div class="thinking" style="color:#a06030">Analyzing behavior {_dots()}</div>'
314
  elif s2_text:
315
  rows_html = ""
316
- for key in KEYS:
317
- m = re.search(rf"{key}[:\s]+(.+)", s2_text, re.IGNORECASE)
318
- val = m.group(1).strip().rstrip(".") if m else "β€”"
319
- if len(val) > 85:
320
- val = val[:82] + "..."
321
- rows_html += f'<div class="bkey">{key}</div><div class="bval">{val}</div>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  s2_content = f'<div class="behavior-row">{rows_html}</div>'
323
  else:
324
  s2_content = '<div class="empty-hint">Waiting...</div>'
325
 
326
- # ── Stage 3 ───────────────────────────────────────────────────────────────
327
  if status == "running3":
328
  s3_content = f'<div class="thinking" style="color:#c0392b">Inferring demographics {_dots()}</div>'
329
  elif s3_text:
330
- pred = conf_raw = reasoning = alts = ""
331
  for line in s3_text.splitlines():
332
  line = line.strip()
333
  if line.startswith("INCOME_PREDICTION:"):
334
  pred = line.replace("INCOME_PREDICTION:", "").strip()
335
- elif line.startswith("INCOME_CONFIDENCE:"):
336
- conf_raw = line.replace("INCOME_CONFIDENCE:", "").strip()
337
  elif line.startswith("INCOME_REASONING:"):
338
  reasoning = line.replace("INCOME_REASONING:", "").strip()
339
  elif line.startswith("ALTERNATIVES:"):
340
  alts = line.replace("ALTERNATIVES:", "").strip()
341
- try:
342
- conf_int = int(re.search(r"\d", conf_raw).group())
343
- except:
344
- conf_int = 3
345
- bar_pct = conf_int * 20
 
 
 
 
 
 
 
 
 
 
346
  alts_html = f'<div class="alternatives">Also possible: <span>{alts}</span></div>' if alts else ""
347
  s3_content = f"""
348
  <div class="pred-block">
349
  <div class="pred-label">Income Prediction</div>
350
  <div class="pred-value">{pred or "β€”"}</div>
351
- <div class="confidence-bar-wrap">
352
- <div class="confidence-bar-bg">
353
- <div class="confidence-bar-fill" style="width:{bar_pct}%"></div>
354
- </div>
355
- <div class="confidence-label">Confidence {conf_int}/5</div>
356
- </div>
357
  <div class="reasoning-text">{reasoning}</div>
358
  {alts_html}
359
  </div>"""
@@ -362,14 +390,15 @@ def render_chain(s1_text, s2_text, s3_text, status="done"):
362
 
363
  PROMPT_SNIPPETS = {
364
  "s1": "You are an expert mobility analyst. Given the trajectory data below, extract: (1) LOCATION INVENTORY β€” list all POI categories visited and visit frequency; (2) TEMPORAL PATTERNS β€” weekly distribution, peak hours; (3) SEQUENCE β€” typical activity chains...",
365
- "s2": "Based on the trajectory features identified: {Response 1}. Now analyze what these mobility patterns reveal about lifestyle: (1) SCHEDULE β€” work/activity routine type; (2) ECONOMIC β€” spending venue tiers; (3) SOCIAL β€” social engagement patterns; (4) LIFESTYLE β€” activity diversity; (5) STABILITY β€” consistency of routine...",
366
- "s3": "Based on feature analysis {Response 1} and behavioral analysis {Response 2}, predict income level. Output format β€” INCOME_PREDICTION: [Low (<$25k) | Lower-middle ($25k–$50k) | Middle ($50k–$100k) | Upper-middle ($100k–$150k) | High (>$150k)]; INCOME_CONFIDENCE: [1–5]; INCOME_REASONING: [detailed reasoning]...",
367
  }
368
- def card(cls, badge, title, content, active):
 
369
  dim = "active" if active else "dim"
370
  prompt = PROMPT_SNIPPETS.get(cls, "")
371
- prompt_html = f'''<div class="prompt-snippet"><span class="prompt-label">Prompt</span>{prompt}</div>''' if prompt else ""
372
- resp_label = '<div class="resp-label">Response</div>' if active and content and "empty-hint" not in content and "thinking" not in content else ""
373
  return f"""
374
  <div class="stage-card {cls} {dim}">
375
  <div class="stage-header">
@@ -378,7 +407,7 @@ def render_chain(s1_text, s2_text, s3_text, status="done"):
378
  </div>
379
  {prompt_html}
380
  {resp_label}
381
- {content}
382
  </div>"""
383
 
384
  def arrow(label, active):
@@ -571,8 +600,8 @@ with gr.Blocks(title="HiCoTraj Demo", theme=gr.themes.Soft()) as app:
571
  gr.Markdown("## HiCoTraj β€” Trajectory Visualization & Hierarchical CoT Demo")
572
  gr.Markdown("*Zero-Shot Demographic Reasoning via Hierarchical Chain-of-Thought Prompting from Trajectory* Β· ACM SIGSPATIAL GeoGenAgent 2025")
573
  gr.Markdown("""
574
- **Dataset:** NUMOSIM[1]
575
- > [1]Stanford C, Adari S, Liao X, et al. *NUMoSim: A Synthetic Mobility Dataset with Anomaly Detection Benchmarks.* ACM SIGSPATIAL Workshop on Geospatial Anomaly Detection, 2024.
576
  """)
577
 
578
  gr.Markdown("### Select Agent")
 
285
 
286
 
287
  def render_chain(s1_text, s2_text, s3_text, status="done"):
 
288
  s1_active = status in ("running1", "running2", "running3", "done")
289
  s2_active = status in ("running2", "running3", "done")
290
  s3_active = status in ("running3", "done")
291
 
292
+ # ── Stage 1: extract bullet lines from free text ──────────────────────────
293
  if status == "running1":
294
  s1_content = f'<div class="thinking">Extracting features {_dots()}</div>'
295
  elif s1_text:
296
  tags = []
297
  for line in s1_text.splitlines():
298
  line = line.strip().lstrip("-").strip()
299
+ # Keep lines that look like "Location: N visits, description"
300
+ if line and len(line) > 8 and len(line) < 80 and not line.endswith(":"):
301
  tags.append(line)
302
+ if len(tags) >= 8:
303
  break
304
+ s1_content = '<div class="tag-row">' + "".join(f'<span class="tag">{t}</span>' for t in tags) + '</div>'
 
 
305
  else:
306
+ s1_content = '<div class="empty-hint">Press β–Ά to start</div>'
307
+
308
+ # ── Stage 2: extract numbered sections as key-value rows ──────────────────
309
+ KEYS = [
310
+ ("SCHEDULE", ["ROUTINE", "SCHEDULE"]),
311
+ ("ECONOMIC", ["ECONOMIC", "SPENDING", "FINANCIAL"]),
312
+ ("SOCIAL", ["SOCIAL", "COMMUNITY"]),
313
+ ("LIFESTYLE", ["LIFESTYLE", "ACTIVITY", "LEISURE"]),
314
+ ("STABILITY", ["STABILITY", "CONSISTENCY", "PATTERN"]),
315
+ ]
316
  if status == "running2":
317
  s2_content = f'<div class="thinking" style="color:#a06030">Analyzing behavior {_dots()}</div>'
318
  elif s2_text:
319
  rows_html = ""
320
+ lines = s2_text.splitlines()
321
+ # Build a searchable blob per section by scanning numbered headings
322
+ sections = {}
323
+ current_key = None
324
+ current_lines = []
325
+ for line in lines:
326
+ line = line.strip()
327
+ # Detect numbered headings like "1. ROUTINE & SCHEDULE ANALYSIS:"
328
+ heading_match = re.match(r'^\d+\.\s+(.+?):\s*$', line)
329
+ if heading_match:
330
+ if current_key:
331
+ sections[current_key] = " ".join(current_lines)
332
+ current_key = heading_match.group(1).upper()
333
+ current_lines = []
334
+ elif current_key and line.startswith("-"):
335
+ current_lines.append(line.lstrip("-").strip())
336
+ if current_key:
337
+ sections[current_key] = " ".join(current_lines)
338
+
339
+ for label, search_words in KEYS:
340
+ val = "β€”"
341
+ for k, v in sections.items():
342
+ if any(w in k for w in search_words):
343
+ # Take first sentence
344
+ first = v.split(".")[0].strip() if v else ""
345
+ val = first[:90] + ("..." if len(first) > 90 else "")
346
+ break
347
+ rows_html += f'<div class="bkey">{label}</div><div class="bval">{val}</div>'
348
  s2_content = f'<div class="behavior-row">{rows_html}</div>'
349
  else:
350
  s2_content = '<div class="empty-hint">Waiting...</div>'
351
 
352
+ # ── Stage 3: prediction ───────────────────────────────────────────────────
353
  if status == "running3":
354
  s3_content = f'<div class="thinking" style="color:#c0392b">Inferring demographics {_dots()}</div>'
355
  elif s3_text:
356
+ pred = reasoning = alts = ""
357
  for line in s3_text.splitlines():
358
  line = line.strip()
359
  if line.startswith("INCOME_PREDICTION:"):
360
  pred = line.replace("INCOME_PREDICTION:", "").strip()
 
 
361
  elif line.startswith("INCOME_REASONING:"):
362
  reasoning = line.replace("INCOME_REASONING:", "").strip()
363
  elif line.startswith("ALTERNATIVES:"):
364
  alts = line.replace("ALTERNATIVES:", "").strip()
365
+ # If reasoning spans multiple lines, grab all of it
366
+ if not reasoning:
367
+ in_reasoning = False
368
+ reasoning_parts = []
369
+ for line in s3_text.splitlines():
370
+ line = line.strip()
371
+ if line.startswith("INCOME_REASONING:"):
372
+ in_reasoning = True
373
+ reasoning_parts.append(line.replace("INCOME_REASONING:", "").strip())
374
+ elif in_reasoning and line and not line.startswith("INCOME_") and not line.startswith("ALTERNATIVES"):
375
+ reasoning_parts.append(line)
376
+ elif in_reasoning and (line.startswith("INCOME_") or line.startswith("ALTERNATIVES")):
377
+ break
378
+ reasoning = " ".join(reasoning_parts).strip()
379
+
380
  alts_html = f'<div class="alternatives">Also possible: <span>{alts}</span></div>' if alts else ""
381
  s3_content = f"""
382
  <div class="pred-block">
383
  <div class="pred-label">Income Prediction</div>
384
  <div class="pred-value">{pred or "β€”"}</div>
 
 
 
 
 
 
385
  <div class="reasoning-text">{reasoning}</div>
386
  {alts_html}
387
  </div>"""
 
390
 
391
  PROMPT_SNIPPETS = {
392
  "s1": "You are an expert mobility analyst. Given the trajectory data below, extract: (1) LOCATION INVENTORY β€” list all POI categories visited and visit frequency; (2) TEMPORAL PATTERNS β€” weekly distribution, peak hours; (3) SEQUENCE β€” typical activity chains...",
393
+ "s2": "Based on the trajectory features identified: {Response 1}. Now analyze what these mobility patterns reveal about lifestyle: (1) SCHEDULE β€” work/activity routine type; (2) ECONOMIC β€” spending venue tiers; (3) SOCIAL β€” social engagement; (4) LIFESTYLE β€” activity diversity; (5) STABILITY β€” consistency of routine...",
394
+ "s3": "Based on feature analysis {Response 1} and behavioral analysis {Response 2}, predict income level. Output β€” INCOME_PREDICTION: [range]; INCOME_REASONING: [detailed reasoning]...",
395
  }
396
+
397
+ def card(cls, badge, title, content_html, active):
398
  dim = "active" if active else "dim"
399
  prompt = PROMPT_SNIPPETS.get(cls, "")
400
+ prompt_html = f'<div class="prompt-snippet"><span class="prompt-label">Prompt</span>{prompt}</div>' if prompt else ""
401
+ resp_label = '<div class="resp-label">Response</div>' if active and content_html and "empty-hint" not in content_html and "thinking" not in content_html else ""
402
  return f"""
403
  <div class="stage-card {cls} {dim}">
404
  <div class="stage-header">
 
407
  </div>
408
  {prompt_html}
409
  {resp_label}
410
+ {content_html}
411
  </div>"""
412
 
413
  def arrow(label, active):
 
600
  gr.Markdown("## HiCoTraj β€” Trajectory Visualization & Hierarchical CoT Demo")
601
  gr.Markdown("*Zero-Shot Demographic Reasoning via Hierarchical Chain-of-Thought Prompting from Trajectory* Β· ACM SIGSPATIAL GeoGenAgent 2025")
602
  gr.Markdown("""
603
+ **Dataset:** NUMOSIM β€” a synthetic mobility dataset with realistic activity patterns across 6,000 agents.
604
+ > Stanford C, Adari S, Liao X, et al. *NUMoSim: A Synthetic Mobility Dataset with Anomaly Detection Benchmarks.* ACM SIGSPATIAL Workshop on Geospatial Anomaly Detection, 2024.
605
  """)
606
 
607
  gr.Markdown("### Select Agent")