Spaces:
Running
Running
Commit Β·
883ef51
1
Parent(s): 626908f
cot
Browse files
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 |
-
|
|
|
|
| 301 |
tags.append(line)
|
| 302 |
-
if len(tags) >=
|
| 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 βΆ
|
| 309 |
-
|
| 310 |
-
# ββ Stage 2 ββββββββββββββββββ
|
| 311 |
-
KEYS = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 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 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 366 |
-
"s3": "Based on feature analysis {Response 1} and behavioral analysis {Response 2}, predict income level. Output
|
| 367 |
}
|
| 368 |
-
|
|
|
|
| 369 |
dim = "active" if active else "dim"
|
| 370 |
prompt = PROMPT_SNIPPETS.get(cls, "")
|
| 371 |
-
prompt_html = f'
|
| 372 |
-
resp_label = '<div class="resp-label">Response</div>' if active and
|
| 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 |
-
{
|
| 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
|
| 575 |
-
>
|
| 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")
|