ginnyxxxxxxx commited on
Commit
64743fe
Β·
1 Parent(s): 883ef51
Files changed (1) hide show
  1. app.py +83 -55
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: 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>"""
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) 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
 
@@ -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))