ginnyxxxxxxx commited on
Commit
244eb3a
Β·
1 Parent(s): e4a4c32
Files changed (1) hide show
  1. app.py +98 -64
app.py CHANGED
@@ -207,37 +207,51 @@ CHAIN_CSS = """
207
  .hct-s3 .hct-title { color: #b0302a; }
208
 
209
  /* ── Prompt pill ── */
210
- .hct-prompt-wrap { padding: 0 14px 8px; }
211
- .hct-prompt-toggle {
212
- display: inline-flex; align-items: center; gap: 5px;
213
- font-family: 'DM Mono', monospace; font-size: 9px;
214
- letter-spacing: 0.08em; text-transform: uppercase;
215
- padding: 3px 9px; border-radius: 20px; cursor: pointer;
216
- border: 1px solid currentColor; opacity: 0.45;
217
- transition: opacity 0.2s; background: transparent;
 
218
  }
219
- .hct-prompt-toggle:hover { opacity: 0.8; }
220
- .hct-s1 .hct-prompt-toggle { color: #3a4a80; }
221
- .hct-s2 .hct-prompt-toggle { color: #7a4a10; }
222
- .hct-s3 .hct-prompt-toggle { color: #b0302a; }
223
- .hct-prompt-box {
224
- display: none;
225
  margin-top: 6px;
226
- background: rgba(0,0,0,0.03);
227
- border-radius: 6px;
228
- padding: 8px 10px;
229
- font-family: 'DM Mono', monospace;
230
- font-size: 10px;
231
- line-height: 1.65;
232
- color: #556;
233
- white-space: pre-wrap;
234
- word-break: break-word;
235
- max-height: 150px;
236
- overflow-y: auto;
237
- border-left: 2px solid currentColor;
238
- opacity: 0.7;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  }
240
- .hct-prompt-box.open { display: block; }
241
 
242
  /* ── Body ── */
243
  .hct-body { padding: 12px 14px; }
@@ -362,15 +376,6 @@ CHAIN_CSS = """
362
  .hct-s2 .hct-dot { background: #c08040; }
363
  .hct-s3 .hct-dot { background: #d4453a; }
364
  </style>
365
- <script>
366
- function hctTogglePrompt(id) {
367
- var box = document.getElementById(id);
368
- var btn = document.getElementById(id + '-btn');
369
- if (!box) return;
370
- var open = box.classList.toggle('open');
371
- btn.textContent = open ? 'β–² hide prompt' : 'β–Ό show prompt';
372
- }
373
- </script>
374
  """
375
 
376
 
@@ -394,10 +399,14 @@ def _parse_s1(text):
394
  locations.append((m.group(1).strip(), int(m.group(2))))
395
  continue
396
 
397
- # Duration
398
  m2 = re.match(r'-?\s*(.+?):\s+(?:Average duration of\s*)?([\d.]+)\s+min(?:utes?)?\s+on average', s, re.IGNORECASE)
399
  if not m2:
400
  m2 = re.match(r'-?\s*(.+?):\s+Average duration of ([\d.]+)\s+min', s, re.IGNORECASE)
 
 
 
 
401
  if m2:
402
  dur_map[m2.group(1).strip()] = float(m2.group(2))
403
 
@@ -500,18 +509,33 @@ def _parse_s3(text):
500
  return pred, conf, (reasoning[:277] + '…' if len(reasoning) > 280 else reasoning)
501
 
502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  def _extract_prompt_instruction(prompt_text, stage):
504
- if not prompt_text:
505
- return ''
506
- key = f'STEP {stage}:'
507
- idx = prompt_text.find(key)
508
- if idx != -1:
509
- return prompt_text[idx:idx + 600].strip()
510
- # fallback: first meaningful line
511
- for line in prompt_text.strip().splitlines():
512
- if line.strip():
513
- return line.strip()[:300]
514
- return ''
515
 
516
 
517
  # ── Body renderers ────────────────────────────────────────────────────────────
@@ -605,27 +629,37 @@ def render_chain(s1_text, s2_text, s3_text, status="idle",
605
  s2_body = _s2_body(s2_text if s2_on else '', s2_on)
606
  s3_body = _s3_body(s3_text if s3_on else '', s3_on)
607
 
608
- def prompt_pill(pid, prompt_text, stage_num):
 
 
609
  instr = _extract_prompt_instruction(prompt_text, stage_num)
610
- if not instr:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  return ''
612
- safe = instr.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
613
- return (f'<div class="hct-prompt-wrap">'
614
- f'<span id="{pid}-btn" class="hct-prompt-toggle" '
615
- f'onclick="hctTogglePrompt(\'{pid}\')">'
616
- f'β–Ό show prompt</span>'
617
- f'<div id="{pid}" class="hct-prompt-box">{safe}</div>'
618
- f'</div>')
619
 
620
- def stage(cls, num, title, body, on, prompt_text, stage_num):
621
  dim_cls = 'active' if on else 'dim'
622
- pill = prompt_pill(f'hct-p-{cls}', prompt_text, stage_num) if on and prompt_text else ''
623
  return (f'<div class="hct-stage hct-{cls} {dim_cls}">'
624
  f'<div class="hct-head">'
625
  f'<span class="hct-num">{num}</span>'
626
  f'<span class="hct-title">{title}</span>'
627
  f'</div>'
628
- f'{pill}'
629
  f'<div class="hct-body">{body}</div>'
630
  f'</div>')
631
 
@@ -637,11 +671,11 @@ def render_chain(s1_text, s2_text, s3_text, status="idle",
637
  f'<div class="hct-arrow-line"></div></div>')
638
 
639
  html = CHAIN_CSS + '<div class="hct-root">'
640
- html += stage('s1', 'Stage 01', 'Feature Extraction', s1_body, s1_on, s1_prompt, 1)
641
  html += arrow('behavioral abstraction', s2_on)
642
- html += stage('s2', 'Stage 02', 'Behavioral Analysis', s2_body, s2_on, s2_prompt, 2)
643
  html += arrow('demographic inference', s3_on)
644
- html += stage('s3', 'Stage 03', 'Demographic Inference', s3_body, s3_on, s3_prompt, 3)
645
  html += '</div>'
646
  return html
647
 
 
207
  .hct-s3 .hct-title { color: #b0302a; }
208
 
209
  /* ── Prompt pill ── */
210
+ /* ── Paper-style prompt+response layout ── */
211
+ .hct-paper-wrap { padding: 0 12px 10px; }
212
+ .hct-paper-prompt {
213
+ background: #fffef5;
214
+ border: 1.5px dashed #c8b870;
215
+ border-radius: 8px;
216
+ padding: 8px 11px;
217
+ margin-bottom: 0;
218
+ position: relative;
219
  }
220
+ .hct-paper-response {
221
+ background: #f8fffe;
222
+ border: 1.5px solid #8ab8a8;
223
+ border-radius: 8px;
224
+ padding: 8px 11px;
 
225
  margin-top: 6px;
226
+ position: relative;
227
+ }
228
+ .hct-s2 .hct-paper-prompt { background: #fffbf0; border-color: #d4a840; }
229
+ .hct-s2 .hct-paper-response { background: #fffbf0; border-color: #c89050; }
230
+ .hct-s3 .hct-paper-prompt { background: #fff8f7; border-color: #d4453a; border-style: dashed; }
231
+ .hct-s3 .hct-paper-response { background: #fff8f7; border-color: #c03030; }
232
+ .hct-paper-tag {
233
+ display: inline-block;
234
+ font-family: 'DM Mono', monospace; font-size: 8.5px; font-weight: 600;
235
+ letter-spacing: 0.1em; text-transform: uppercase;
236
+ padding: 1px 6px; border-radius: 3px; margin-bottom: 5px;
237
+ }
238
+ .hct-paper-prompt .hct-paper-tag { background: #f0e48a; color: #7a6010; }
239
+ .hct-s2 .hct-paper-prompt .hct-paper-tag { background: #f5d580; color: #7a5010; }
240
+ .hct-s3 .hct-paper-prompt .hct-paper-tag { background: #fac8c4; color: #a03028; }
241
+ .hct-paper-response .hct-paper-tag { background: #c8e8d8; color: #206048; }
242
+ .hct-s2 .hct-paper-response .hct-paper-tag { background: #f0dcb0; color: #7a5010; }
243
+ .hct-s3 .hct-paper-response .hct-paper-tag { background: #f8c8c4; color: #902828; }
244
+ .hct-paper-text {
245
+ font-size: 11px; line-height: 1.6; color: #333;
246
+ white-space: pre-wrap; word-break: break-word;
247
+ }
248
+ .hct-paper-connector {
249
+ display: flex; align-items: center; justify-content: center;
250
+ height: 14px; margin: 0 20px;
251
+ }
252
+ .hct-paper-connector-line {
253
+ width: 1px; height: 100%; background: #aaa;
254
  }
 
255
 
256
  /* ── Body ── */
257
  .hct-body { padding: 12px 14px; }
 
376
  .hct-s2 .hct-dot { background: #c08040; }
377
  .hct-s3 .hct-dot { background: #d4453a; }
378
  </style>
 
 
 
 
 
 
 
 
 
379
  """
380
 
381
 
 
399
  locations.append((m.group(1).strip(), int(m.group(2))))
400
  continue
401
 
402
+ # Duration β€” 4 formats
403
  m2 = re.match(r'-?\s*(.+?):\s+(?:Average duration of\s*)?([\d.]+)\s+min(?:utes?)?\s+on average', s, re.IGNORECASE)
404
  if not m2:
405
  m2 = re.match(r'-?\s*(.+?):\s+Average duration of ([\d.]+)\s+min', s, re.IGNORECASE)
406
+ if not m2:
407
+ m2 = re.match(r'-?\s*Average duration at (.+?):\s+([\d.]+)\s+min', s, re.IGNORECASE)
408
+ if not m2:
409
+ m2 = re.search(r'\bat ([A-Za-z][^(,]+?)\s*\(average ([\d.]+)\s*min', s, re.IGNORECASE)
410
  if m2:
411
  dur_map[m2.group(1).strip()] = float(m2.group(2))
412
 
 
509
  return pred, conf, (reasoning[:277] + '…' if len(reasoning) > 280 else reasoning)
510
 
511
 
512
+ PROMPT_DESCRIPTIONS = {
513
+ 1: (
514
+ "Given the agent's mobility trajectory data β€” including visited locations, visit frequencies, "
515
+ "stay durations, activity types, and temporal patterns β€” extract objective factual features "
516
+ "without any interpretation. Identify: (1) location inventory with visit counts and apparent "
517
+ "price tier; (2) temporal distribution across time-of-day and weekday/weekend; "
518
+ "(3) spatial activity radius and movement distances; (4) typical activity sequences and "
519
+ "transition patterns between location types."
520
+ ),
521
+ 2: (
522
+ "Based on the trajectory features extracted in Step 1, perform behavioral abstraction across "
523
+ "four dimensions: (1) Routine & Schedule β€” infer work schedule type and daily structure; "
524
+ "(2) Economic Behavior β€” assess spending tier from venue choices and mobility costs; "
525
+ "(3) Social & Lifestyle β€” identify social engagement patterns, leisure activities, and "
526
+ "community involvement; (4) Routine Stability β€” evaluate consistency and regularity of "
527
+ "movement patterns over the observation period."
528
+ ),
529
+ 3: (
530
+ "Synthesizing the factual features (Step 1) and behavioral patterns (Step 2), infer the "
531
+ "agent's household income bracket. Consider location economic indicators, transportation "
532
+ "mode signals, activity diversity, and lifestyle markers collectively. Output a single "
533
+ "income range prediction with supporting reasoning grounded in the observed mobility evidence."
534
+ ),
535
+ }
536
+
537
  def _extract_prompt_instruction(prompt_text, stage):
538
+ return PROMPT_DESCRIPTIONS.get(stage, '')
 
 
 
 
 
 
 
 
 
 
539
 
540
 
541
  # ── Body renderers ────────────────────────────────────────────────────────────
 
629
  s2_body = _s2_body(s2_text if s2_on else '', s2_on)
630
  s3_body = _s3_body(s3_text if s3_on else '', s3_on)
631
 
632
+ def paper_block(prompt_text, stage_num, response_text, resp_label):
633
+ def esc(t): return t.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
634
+ # Prompt snippet
635
  instr = _extract_prompt_instruction(prompt_text, stage_num)
636
+ p_short = esc(instr[:220]) + ('……' if len(instr) > 220 else '') if instr else ''
637
+ # Response snippet
638
+ r_clean = response_text.strip()
639
+ r_short = esc(r_clean[:260]) + ('……' if len(r_clean) > 260 else '') if r_clean else ''
640
+
641
+ prompt_html = (f'<div class="hct-paper-prompt">'
642
+ f'<div class="hct-paper-tag">Prompt {stage_num}</div>'
643
+ f'<div class="hct-paper-text">{p_short}</div>'
644
+ f'</div>') if p_short else ''
645
+ connector = '<div class="hct-paper-connector"><div class="hct-paper-connector-line"></div></div>' if (p_short and r_short) else ''
646
+ response_html = (f'<div class="hct-paper-response">'
647
+ f'<div class="hct-paper-tag">{resp_label}</div>'
648
+ f'<div class="hct-paper-text">{r_short}</div>'
649
+ f'</div>') if r_short else ''
650
+ if not prompt_html and not response_html:
651
  return ''
652
+ return f'<div class="hct-paper-wrap">{prompt_html}{connector}{response_html}</div>'
 
 
 
 
 
 
653
 
654
+ def stage(cls, num, title, body, on, prompt_text, stage_num, response_text='', resp_label='Response'):
655
  dim_cls = 'active' if on else 'dim'
656
+ paper = paper_block(prompt_text, stage_num, response_text, resp_label) if on and (prompt_text or response_text) else ''
657
  return (f'<div class="hct-stage hct-{cls} {dim_cls}">'
658
  f'<div class="hct-head">'
659
  f'<span class="hct-num">{num}</span>'
660
  f'<span class="hct-title">{title}</span>'
661
  f'</div>'
662
+ f'{paper}'
663
  f'<div class="hct-body">{body}</div>'
664
  f'</div>')
665
 
 
671
  f'<div class="hct-arrow-line"></div></div>')
672
 
673
  html = CHAIN_CSS + '<div class="hct-root">'
674
+ html += stage('s1', 'Stage 01', 'Feature Extraction', s1_body, s1_on, s1_prompt, 1, s1_text, 'Response 1')
675
  html += arrow('behavioral abstraction', s2_on)
676
+ html += stage('s2', 'Stage 02', 'Behavioral Analysis', s2_body, s2_on, s2_prompt, 2, s2_text, 'Response 2')
677
  html += arrow('demographic inference', s3_on)
678
+ html += stage('s3', 'Stage 03', 'Demographic Inference', s3_body, s3_on, s3_prompt, 3, s3_text, 'Response 3')
679
  html += '</div>'
680
  return html
681