Event Analysis Team · System Architecture

v4.2.0 — Composer-Emitted Charts & Maps

UnifiedComposer (Opus 4.7) 가 분석·본문·차트·지도까지 모두 단일 호출로 emit. 외부 결정적 빌더와 maplibre 의존을 폐기하고 mono guide 정합으로 통일. 보고서 자유도 + 시각화 자유도가 한 LLM 의 판단으로 수렴.

v4.2.0 main · f8e68ec 2-call · Opus 4.7 × 2 Light Mono Theme d3-geo + TopoJSON Mono Guide §4 patterns

한 화면 요약

입력: 텔레그램에 사건 한 줄. 심층/짧게/요약 키워드로 모드 자동 결정. 모든 모드 LLM 호출 2회.

처리: 1) 상황 분석관 (Opus 4.7, 웹 검색) — 사실 / 타임라인 / 핵심 수치 / 출처 URL 수집 → 2) 편집장 (Opus 4.7, 단일 호출) — 행위자 / 구조 / 시나리오 / 모순 분석 + 보고서 본문 + 감시 신호 + 차트 8종 데이터 + 지도 데이터 모두 emit → 3) HTML 렌더 + Cloudflare Pages 배포.

v4.0.0~v4.1.0 vs v4.2.0 차이: 이전엔 차트/지도가 전혀 안 박혔음 (composer 가 chart-id referencing 해도 chart_payload 가 비어있어 매칭 실패). v4.2.0 에서 composer 가 차트 데이터를 직접 dict 로 emit, charts.js 가 inline payload 로 렌더. 지도도 composer 가 lat/lng 직접 emit, maplibre 폐기하고 d3 + d3-geo + TopoJSON 으로 통일.

핵심 안전장치: 수치 비교가 본문 이해에 결정적일 때만 차트 emit (composer 시스템 프롬프트). 데이터 없으면 차트 0개. 무지성 박힘 영구 차단.

파이프라인 — Before vs After

v3.5.0 까지 5~13단계 + 차트는 결정적 빌더 자동 추출. v4.2.0 부터 4단계 + 차트/지도 모두 composer 가 직접 emit.

v3.5.0 → v4.1.0 (BEFORE)

차트/지도가 안 나옴

  1. visual_builder.build_chart_payload() 호출 안 함 (Tier 4 에서 제거)
  2. composer 가 embedded_charts 에 chart-id 적어도
  3. freeform_essay.html 의 chart_payload 가 빈 dict → 매칭 실패
  4. 결과: 차트 0개. 본문 텍스트만
  5. 지도도 동일. result.visuals.leaflet_config 가 None
시각화: 전혀 없음. 본문에 묻혀버린 정보 비교는 텍스트로만.
v4.2.0 (AFTER)

composer 가 데이터까지 emit

  1. composer SYSTEM_PROMPT 에 차트 8종 type / data 스키마 명시
  2. composer 가 ComposedSection.charts 에 dict list 출력
  3. composer 가 ComposedReport.embedded_map 에 지도 데이터 (필요 시)
  4. freeform_essay.html 이 inline payload + SVG 컨테이너 emit
  5. charts.js + maps.js 가 mono guide 패턴으로 SVG 렌더
시각화: 본문 이해에 결정적일 때만. composer 가 의미와 데이터를 같은 호출에서 결정.
왜 composer 가 데이터까지 emit 해야 하나

이전 v3.x 의 결정적 빌더 (build_chart_payload) 는 분석 결과 데이터에서 9종 차트를 자동 추출. 그 차트를 박는지 (= 본문에서 referencing 하는지) 와 데이터 추출이 분리되어 있어 무관한 차트가 양산됨. v4.2.0 은 둘을 합침 — composer 가 본문을 짜면서 "이 단락은 도넛으로 보여주는 게 결정적" 이라고 판단할 때만 차트 emit. 외부 빌더 0줄.

4단계 파이프라인

상황 분석관 — ContextAnalyst
claude-opus-4-7 · 웹 검색 활성 · v4.1.0 부터 Opus
사건의 사실 / 타임라인 / 핵심 수치 / 출처 URL 수집. composer 가 보는 *유일한* 사실 입력이라 사실 추출 품질이 보고서 전체 품질의 상한선. 출처 1차/2차 구분 / 단위 보존 / 인과 순서 모두 Opus 수준 reasoning 필요.
편집장 — UnifiedComposer ⭐
claude-opus-4-7 · 단일 LLM 호출 · max 8K tokens
ContextAnalysis 만 받아 다음을 *모두* 수행:
  1. 핵심 행위자 식별
  2. 구조적 동인 분석
  3. 인과 사슬 추적
  4. 시나리오 설계 (3~5개)
  5. 모순 / 반대 가설 표면화
  6. 보고서 본문 작성 (자유 형식)
  7. 감시 신호 추출 (Watchlist 통합)
  8. (v4.2.0) 차트 데이터 emit — 본문 이해에 결정적일 때만, type 8종 중 선택
  9. (v4.2.0) 지도 데이터 emit — 명백히 지리적 사건일 때만
보고서 합성 — ReportSynthesizer
코드 결정적 · LLM 호출 0
composer 의 ComposedReportfreeform_essay.html 에 주입해 mono 테마 HTML 생성. lens_policy.select_theme() 가 사건 카테고리에서 burgundy_mono / light_mono 자동 선택. 차트는 charts.js (inline payload 스캔), 지도는 maps.js (d3-geo + TopoJSON) 가 SVG 로 렌더. wrangler 로 Cloudflare Pages 배포.
Watchlist 등록
SQLite · LLM 호출 0
composed_report.watch_signals 를 SQLite Registry 에 INSERT. 텔레그램 /watchlist, /fire 명령으로 후속 추적.

차트 type 8종 (composer-emitted)

composer 가 ComposedSection.charts 의 각 dict 로 type 1개씩 emit. type 별 data 스키마는 composer SYSTEM_PROMPT 가 SSOT. charts.js 가 inline payload 스캔 후 mono guide §4 패턴으로 SVG 렌더.

type용도data 스키마게이팅
donut비중 비교[{label, value:number, note?}]≥3개 + non-uniform
bar순위·분포[{label, value:number, note?}]≥1
line시계열 추이[{x, y:number, event?}]≥2 점
gantt사건 구간[{label, start, end, note?}]≥1
network관계도{nodes:[{id,label,group?}], links:[{source,target,type?}]}nodes ≥2
stacked시나리오 × 행위자 영향{scenarios:[{name, segments:[{label,value}]}]}≥1 row
bubble확률 × 영향 매트릭스[{label, x:number, y:number, size?}]≥1
heatmap단계별 위험도[{title, severity:'low'|'medium'|'high'}]≥1

Mono guide §4 패턴 시스템

색이 mono 한 hue 로 수렴하므로 카테고리 구분은 45° 패턴 + 액센트 솔리드. 핵심 항목은 accent solid, 그 외는 패턴 순환 (hatch-tight → accent-hatch → hatch-wide → dots).

accent solid · 핵심
hatch-tight
accent-hatch
hatch-wide
dots

demo — donut 차트 (light_mono)

호르무즈 의존 비중 (가상 데이터)
한국 31% 일본 24% 대만 17% 인도 15% 중국 13%

지도 시스템 — d3 + d3-geo + TopoJSON

v4.2.0 에서 maplibre-gl 의존 폐기 (mono guide §2). composer 가 ComposedReport.embedded_map 에 lat/lng + arcs 를 직접 emit, maps.js 가 d3.geoMercator + world-atlas/110m TopoJSON 으로 정적 SVG 렌더.

왜 maplibre 폐기인가 (mono guide §2.2)

외부 타일 서비스 / 글리프 PBF 호출 / 스키마 변경에 취약. 샘플 페이지가 빈 배경으로 떨어지는 회귀 발생. 모바일 ISP 차단 / CDN 캐시 / Content-Type 문제로 신뢰할 수 없음. world-atlas 110m TopoJSON 한 번 fetch (~100KB) 로 전 세계 국경 벡터를 받아 정적 SVG 로 렌더 → 인쇄·캡처 모두 안정적.

embedded_map = { "center": [lng, lat], "zoom": float, "markers": [ {"id": "hormuz", "name": "호르무즈", "lng": 56.4, "lat": 26.6, "highlight": true} ], "arcs": [ {"from_id": "hormuz", "to_id": "singapore", "highlight": true, "label": "원유 회랑"} ], "legend": [ {"label": "중점 회랑", "kind": "line", "highlight": true} ] }

호 색상은 highlight 여부로 accent / muted 분리. 1차 회랑 (highlight=true) 은 실선 굵게, 보조 항로는 점선 얇게. 마커는 highlight 만 accent + 굵게. 베이스맵은 mono 토큰 (--map-land / --map-water / --map-boundary) 으로 동적 적용.

ComposedReport 스키마 (v4.2.0)

composer 가 emit 하는 단일 출력 객체. v4.0.0 → v4.2.0 사이 추가된 필드는 + 표시.

class ComposedReport(BaseModel): headline: str deck: str = "" sections: list[ComposedSection] closing: str = "" # v4.0.0 — Tier 4 통합 출력 watch_signals: list[dict] # Watchlist Registry 통합 contradictions: list[dict] # Anti-pattern #5 보존 confidence_summary: str = "" # 한 줄 자유 평가 confidence_score: float = 0.5 # 0.0~1.0 # v4.2.0 — 지도 (보고서당 1개, 지리적 사건일 때만) embedded_map: dict | None = None # {center, zoom, markers, arcs, legend?} class ComposedSection(BaseModel): heading: str kicker: str = "" prose: str # v4.2.0 — composer 가 차트 데이터를 inline 으로 emit charts: list[dict] # [{type, title, data, note?}] embedded_charts: list[str] # [legacy] chart-id 참조, 호환만 embedded_blocks: list[str] # actor_cards 등 (선택) pull_quote: str = "" cited_claim_ids: list[str]

디렉토리 변화 — v4.2.0

취소선 = 코드 보존하되 호출 안 됨. 초록 = v4.x 신규 또는 강화. 추후 cleanup commit 시 deprecated 모듈 일괄 제거 예정.

src/ ├── orchestrator.py ← 4단계 단순화 (~120줄). VERSION = v4.2.0 ├── models.py+ ComposedSection.charts + ComposedReport.embedded_map ├── token_budget.py ← 모든 모드 max_llm_calls=2. mode = composer prompt 깊이 지시 ├── lens_policy.py ← select_theme() 만 사용. select_lenses() 호출 안 됨 ├── visual_builder.py ← v4.2.0 호출 안 됨 (composer 가 직접 emit 으로 대체) ├── agents/ │ ├── context_analyst.py ← Opus 4.7 (v4.1.0) │ ├── narrative_composer.py ← Opus 4.7. + 차트/지도 emit prompt │ ├── report_synthesizer.py ← HTML 렌더 + Cloudflare 배포 │ ├── player_analyst.py ← deprecated │ ├── dynamics_analyst.py ← deprecated │ ├── chain_reaction_analyst.py ← deprecated │ ├── scenario_architect.py ← deprecated │ ├── synthesis_judge.py ← deprecated │ ├── quality_inspector.py ← deprecated │ └── visual_analyst.py ← deprecated ├── lenses/ ← 11종 모두 deprecated ├── archetypes/ │ └── freeform_essay.py유일하게 사용 └── templates/ ├── report.css ← Mono 2테마 (burgundy_mono + light_mono) ├── archetypes/ │ └── freeform_essay.html+ inline charts/map 렌더 ├── static/ │ ├── d3.v7.min.js │ ├── charts.jsv4.2.0 전면 재작성 (inline payload + mono 패턴) │ ├── charts.css │ ├── maps.jsv4.2.0 전면 재작성 (d3-geo + TopoJSON, maplibre 폐기) │ └── maps.cssv4.2.0 재작성 (mono 토큰만) ├── report.html ← deprecated (six_act_theater) ├── report_block.html ← deprecated (옛 archetype 디스패처) └── blocks/ ← composer.embedded_blocks 명시 시만, 실질 미사용 samples/ ├── v4_2_0_architecture.html본 페이지 (light_mono) ├── v4_0_0_architecture.html ← v4.0.0 아카이브 (burgundy_mono) ├── v3_5_0_architecture.html ← v3.5.0 아카이브 └── chart_map_mono_compare.html ← mono guide 살아있는 비교 샘플

운영 — VM 봇 재기동

v4.2.0 변경은 코드 + 모델 + 템플릿 + 정적 자산 4개 영역 모두 영향. VM 재기동 필수.

재기동 절차

cd ~/agents_reviewer
git pull origin main
pkill -9 -f 'python -m src.main' # 옛 인스턴스 일괄 종료 (literal <PID> 입력 실수 회피)
sleep 2
source venv/bin/activate
nohup python -m src.main > bot.log 2>&1 &
disown
sleep 5 && tail -30 bot.log

확인: 텔레그램 /statusVERSION: v4.2.0 + 에이전트 구성 2명 (상황 분석관 / 편집장 모두 Opus 4.7) 표시. 분석 후 보고서 footer 에 archetype: freeform_essay 표시.