Spaces:
Running
Running
Commit Β·
64743fe
1
Parent(s): 883ef51
cot
Browse files
app.py
CHANGED
|
@@ -246,6 +246,7 @@ CHAIN_CSS = """
|
|
| 246 |
|
| 247 |
.thinking { font-size: 13px; color: #888; padding: 8px 0; }
|
| 248 |
.empty-hint { font-size: 12px; color: #ccc; padding: 6px 0; }
|
|
|
|
| 249 |
|
| 250 |
.prompt-snippet {
|
| 251 |
font-size: 11px; color: #888; line-height: 1.5;
|
|
@@ -289,108 +290,137 @@ def render_chain(s1_text, s2_text, s3_text, status="done"):
|
|
| 289 |
s2_active = status in ("running2", "running3", "done")
|
| 290 |
s3_active = status in ("running3", "done")
|
| 291 |
|
| 292 |
-
# ββ Stage 1
|
| 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()
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
break
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
| 305 |
else:
|
| 306 |
s1_content = '<div class="empty-hint">Press βΆ to start</div>'
|
| 307 |
|
| 308 |
-
# ββ Stage 2
|
| 309 |
KEYS = [
|
| 310 |
("SCHEDULE", ["ROUTINE", "SCHEDULE"]),
|
| 311 |
("ECONOMIC", ["ECONOMIC", "SPENDING", "FINANCIAL"]),
|
| 312 |
-
("SOCIAL", ["SOCIAL", "COMMUNITY"]),
|
| 313 |
-
("
|
| 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 |
-
|
| 320 |
-
lines = s2_text.splitlines()
|
| 321 |
-
# Build a searchable blob per section by scanning numbered headings
|
| 322 |
sections = {}
|
| 323 |
current_key = None
|
| 324 |
-
|
| 325 |
-
for line in
|
| 326 |
line = line.strip()
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
if heading_match:
|
| 330 |
if current_key:
|
| 331 |
-
sections[current_key] =
|
| 332 |
-
current_key =
|
| 333 |
-
|
| 334 |
elif current_key and line.startswith("-"):
|
| 335 |
-
|
|
|
|
|
|
|
| 336 |
if current_key:
|
| 337 |
-
sections[current_key] =
|
| 338 |
|
|
|
|
| 339 |
for label, search_words in KEYS:
|
| 340 |
val = "β"
|
| 341 |
-
for k,
|
| 342 |
-
if any(w in k for w in search_words):
|
| 343 |
-
# Take first
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 =
|
| 357 |
-
|
| 358 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 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">{
|
| 386 |
-
{alts_html}
|
| 387 |
</div>"""
|
| 388 |
else:
|
| 389 |
s3_content = '<div class="empty-hint">Waiting...</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)
|
| 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 |
|
|
@@ -430,8 +460,6 @@ def render_chain(s1_text, s2_text, s3_text, status="done"):
|
|
| 430 |
return html
|
| 431 |
|
| 432 |
|
| 433 |
-
# ββ Map & demo ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 434 |
-
|
| 435 |
def build_map(agent_sp):
|
| 436 |
agent_sp = agent_sp.reset_index(drop=True).copy()
|
| 437 |
agent_sp["latitude"] += np.random.uniform(-0.0003, 0.0003, len(agent_sp))
|
|
|
|
| 246 |
|
| 247 |
.thinking { font-size: 13px; color: #888; padding: 8px 0; }
|
| 248 |
.empty-hint { font-size: 12px; color: #ccc; padding: 6px 0; }
|
| 249 |
+
.temporal-line { font-size: 11px; color: #666; margin-top: 8px; font-family: 'IBM Plex Mono', monospace; }
|
| 250 |
|
| 251 |
.prompt-snippet {
|
| 252 |
font-size: 11px; color: #888; line-height: 1.5;
|
|
|
|
| 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 |
+
# Parse LOCATION INVENTORY bullets: "- Name: N visits, description"
|
| 299 |
+
in_inventory = False
|
| 300 |
for line in s1_text.splitlines():
|
| 301 |
+
line = line.strip()
|
| 302 |
+
if "LOCATION INVENTORY" in line.upper():
|
| 303 |
+
in_inventory = True
|
| 304 |
+
continue
|
| 305 |
+
if in_inventory:
|
| 306 |
+
if line.startswith("TEMPORAL") or line.startswith("SEQUENCE") or (line and not line.startswith("-") and not line.startswith("*") and len(line) > 40):
|
| 307 |
+
break
|
| 308 |
+
if line.startswith("-"):
|
| 309 |
+
# "- Name: N visits, type" or "- Name (N visits)"
|
| 310 |
+
clean = line.lstrip("-").strip()
|
| 311 |
+
# Shorten: keep "Name (N visits)" style
|
| 312 |
+
m = re.match(r'(.+?):\s*(\d+)\s*visit', clean, re.IGNORECASE)
|
| 313 |
+
if m:
|
| 314 |
+
name = m.group(1).strip()
|
| 315 |
+
n = m.group(2)
|
| 316 |
+
tags.append(f"{name} Β· {n}x")
|
| 317 |
+
elif clean:
|
| 318 |
+
tags.append(clean[:55])
|
| 319 |
+
if len(tags) >= 8:
|
| 320 |
+
break
|
| 321 |
+
|
| 322 |
+
# Fallback: also grab temporal summary line
|
| 323 |
+
temporal_line = ""
|
| 324 |
+
for line in s1_text.splitlines():
|
| 325 |
+
line = line.strip()
|
| 326 |
+
if "weekly distribution" in line.lower() or "weekday" in line.lower():
|
| 327 |
+
temporal_line = line.lstrip("-").strip()[:70]
|
| 328 |
break
|
| 329 |
+
|
| 330 |
+
tag_html = "".join(f'<span class="tag">{t}</span>' for t in tags)
|
| 331 |
+
temp_html = f'<div class="temporal-line">β± {temporal_line}</div>' if temporal_line else ""
|
| 332 |
+
s1_content = f'<div class="tag-row">{tag_html}</div>{temp_html}'
|
| 333 |
else:
|
| 334 |
s1_content = '<div class="empty-hint">Press βΆ to start</div>'
|
| 335 |
|
| 336 |
+
# ββ Stage 2 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 337 |
KEYS = [
|
| 338 |
("SCHEDULE", ["ROUTINE", "SCHEDULE"]),
|
| 339 |
("ECONOMIC", ["ECONOMIC", "SPENDING", "FINANCIAL"]),
|
| 340 |
+
("SOCIAL", ["SOCIAL", "COMMUNITY", "LIFESTYLE"]),
|
| 341 |
+
("STABILITY", ["STABILITY", "CONSISTENCY", "REGULARITY"]),
|
|
|
|
| 342 |
]
|
| 343 |
if status == "running2":
|
| 344 |
s2_content = f'<div class="thinking" style="color:#a06030">Analyzing behavior {_dots()}</div>'
|
| 345 |
elif s2_text:
|
| 346 |
+
# Parse numbered sections
|
|
|
|
|
|
|
| 347 |
sections = {}
|
| 348 |
current_key = None
|
| 349 |
+
current_bullets = []
|
| 350 |
+
for line in s2_text.splitlines():
|
| 351 |
line = line.strip()
|
| 352 |
+
m = re.match(r'^\d+\.\s+(.+?)(?:\s+ANALYSIS)?(?:\s+PATTERNS)?(?:\s+INDICATORS)?:\s*$', line, re.IGNORECASE)
|
| 353 |
+
if m:
|
|
|
|
| 354 |
if current_key:
|
| 355 |
+
sections[current_key] = current_bullets
|
| 356 |
+
current_key = m.group(1).upper()
|
| 357 |
+
current_bullets = []
|
| 358 |
elif current_key and line.startswith("-"):
|
| 359 |
+
bullet = line.lstrip("-").strip()
|
| 360 |
+
if bullet:
|
| 361 |
+
current_bullets.append(bullet)
|
| 362 |
if current_key:
|
| 363 |
+
sections[current_key] = current_bullets
|
| 364 |
|
| 365 |
+
rows_html = ""
|
| 366 |
for label, search_words in KEYS:
|
| 367 |
val = "β"
|
| 368 |
+
for k, bullets in sections.items():
|
| 369 |
+
if any(w in k for w in search_words) and bullets:
|
| 370 |
+
# Take first bullet, truncate at 2 sentences
|
| 371 |
+
text = bullets[0]
|
| 372 |
+
sentences = re.split(r'(?<=[.!?])\s+', text)
|
| 373 |
+
val = " ".join(sentences[:2])
|
| 374 |
+
if len(val) > 100:
|
| 375 |
+
val = val[:97] + "..."
|
| 376 |
break
|
| 377 |
rows_html += f'<div class="bkey">{label}</div><div class="bval">{val}</div>'
|
| 378 |
s2_content = f'<div class="behavior-row">{rows_html}</div>'
|
| 379 |
else:
|
| 380 |
s2_content = '<div class="empty-hint">Waiting...</div>'
|
| 381 |
|
| 382 |
+
# ββ Stage 3 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 383 |
if status == "running3":
|
| 384 |
s3_content = f'<div class="thinking" style="color:#c0392b">Inferring demographics {_dots()}</div>'
|
| 385 |
elif s3_text:
|
| 386 |
+
pred = reasoning = ""
|
| 387 |
+
lines = s3_text.splitlines()
|
| 388 |
+
i = 0
|
| 389 |
+
while i < len(lines):
|
| 390 |
+
line = lines[i].strip()
|
| 391 |
if line.startswith("INCOME_PREDICTION:"):
|
| 392 |
pred = line.replace("INCOME_PREDICTION:", "").strip()
|
| 393 |
elif line.startswith("INCOME_REASONING:"):
|
| 394 |
reasoning = line.replace("INCOME_REASONING:", "").strip()
|
| 395 |
+
# Collect continuation lines until blank or next key
|
| 396 |
+
i += 1
|
| 397 |
+
while i < len(lines):
|
| 398 |
+
nxt = lines[i].strip()
|
| 399 |
+
if not nxt or nxt.startswith("INCOME_") or re.match(r'^\d+\.', nxt):
|
| 400 |
+
break
|
| 401 |
+
reasoning += " " + nxt
|
| 402 |
+
i += 1
|
| 403 |
+
continue
|
| 404 |
+
i += 1
|
| 405 |
+
|
| 406 |
+
# Truncate reasoning to ~2 sentences
|
| 407 |
+
sentences = re.split(r'(?<=[.!?])\s+', reasoning.strip())
|
| 408 |
+
short_reasoning = " ".join(sentences[:2])
|
| 409 |
+
if len(short_reasoning) > 160:
|
| 410 |
+
short_reasoning = short_reasoning[:157] + "..."
|
| 411 |
|
|
|
|
| 412 |
s3_content = f"""
|
| 413 |
<div class="pred-block">
|
| 414 |
<div class="pred-label">Income Prediction</div>
|
| 415 |
<div class="pred-value">{pred or "β"}</div>
|
| 416 |
+
<div class="reasoning-text">{short_reasoning}</div>
|
|
|
|
| 417 |
</div>"""
|
| 418 |
else:
|
| 419 |
s3_content = '<div class="empty-hint">Waiting...</div>'
|
| 420 |
|
| 421 |
PROMPT_SNIPPETS = {
|
| 422 |
"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...",
|
| 423 |
+
"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) STABILITY β consistency of routine...",
|
| 424 |
"s3": "Based on feature analysis {Response 1} and behavioral analysis {Response 2}, predict income level. Output β INCOME_PREDICTION: [range]; INCOME_REASONING: [detailed reasoning]...",
|
| 425 |
}
|
| 426 |
|
|
|
|
| 460 |
return html
|
| 461 |
|
| 462 |
|
|
|
|
|
|
|
| 463 |
def build_map(agent_sp):
|
| 464 |
agent_sp = agent_sp.reset_index(drop=True).copy()
|
| 465 |
agent_sp["latitude"] += np.random.uniform(-0.0003, 0.0003, len(agent_sp))
|