seawolf2357 commited on
Commit
bbd74a7
ยท
verified ยท
1 Parent(s): bc2af8c

Upload report_enhancement.py

Browse files
Files changed (1) hide show
  1. report_enhancement.py +1829 -0
report_enhancement.py ADDED
@@ -0,0 +1,1829 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AETHER Proto-AGI v2.2 - Report Enhancement Module
3
+ ์ถœ์ฒ˜ ๊ด€๋ฆฌ, ์œ ํ˜•๋ณ„ ํฌ๋งทํ„ฐ, ์ „๋ฌธ ๋ณด๊ณ ์„œ ์ƒ์„ฑ๊ธฐ
4
+
5
+ ์ด ํŒŒ์ผ์˜ ํด๋ž˜์Šค๋“ค์„ core.py์— ํ†ตํ•ฉํ•˜์„ธ์š”.
6
+ ๊ธฐ์กด ReportGenerator ํด๋ž˜์Šค๋ฅผ ์•„๋ž˜ ProfessionalReportGenerator๋กœ ๊ต์ฒดํ•˜์„ธ์š”.
7
+ """
8
+
9
+ import json
10
+ import re
11
+ from datetime import datetime
12
+ from dataclasses import dataclass, field
13
+ from typing import Optional, List, Dict, Any, Tuple
14
+ from enum import Enum
15
+
16
+
17
+ # ==================== SourceManager (P0-2) ====================
18
+
19
+ @dataclass
20
+ class WebSource:
21
+ """์›น ๊ฒ€์ƒ‰ ์ถœ์ฒ˜"""
22
+ title: str
23
+ url: str
24
+ snippet: str
25
+ element: str # ์–ด๋А ์˜คํ–‰ ๋‹จ๊ณ„์—์„œ ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€
26
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
27
+ relevance_score: float = 0.0
28
+
29
+ @dataclass
30
+ class KnowledgeSource:
31
+ """์ง€์‹ DB ์ถœ์ฒ˜"""
32
+ knowledge_id: str
33
+ goal: str
34
+ quality_score: float
35
+ element: str
36
+ created_at: str
37
+ snippet: str
38
+
39
+ @dataclass
40
+ class CrawlSource:
41
+ """URL ํฌ๋กค๋ง ์ถœ์ฒ˜"""
42
+ url: str
43
+ title: str
44
+ content_preview: str
45
+ crawled_at: str
46
+ success: bool
47
+
48
+
49
+ class SourceManager:
50
+ """
51
+ ์ถœ์ฒ˜/์ธ์šฉ ํ†ตํ•ฉ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ
52
+
53
+ ๋ชจ๋“  ๋ถ„์„ ๊ณผ์ •์—์„œ ์‚ฌ์šฉ๋œ ์ถœ์ฒ˜๋ฅผ ์ถ”์ ํ•˜๊ณ 
54
+ ์ตœ์ข… ๋ณด๊ณ ์„œ์— ์ฒด๊ณ„์ ์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
55
+ """
56
+
57
+ def __init__(self):
58
+ self.web_sources: List[WebSource] = []
59
+ self.knowledge_sources: List[KnowledgeSource] = []
60
+ self.crawl_sources: List[CrawlSource] = []
61
+ self._citation_counter = 0
62
+ self._citation_map: Dict[str, int] = {} # url/id -> citation number
63
+
64
+ def reset(self):
65
+ """์ƒˆ ๋ถ„์„ ์„ธ์…˜ ์‹œ์ž‘ ์‹œ ์ดˆ๊ธฐํ™”"""
66
+ self.web_sources = []
67
+ self.knowledge_sources = []
68
+ self.crawl_sources = []
69
+ self._citation_counter = 0
70
+ self._citation_map = {}
71
+
72
+ # ===== ์ถœ์ฒ˜ ์ถ”๊ฐ€ =====
73
+
74
+ def add_web_source(self, title: str, url: str, snippet: str,
75
+ element: str, relevance_score: float = 0.0) -> int:
76
+ """์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ถœ์ฒ˜ ์ถ”๊ฐ€"""
77
+ if url in self._citation_map:
78
+ return self._citation_map[url]
79
+
80
+ self._citation_counter += 1
81
+ citation_num = self._citation_counter
82
+ self._citation_map[url] = citation_num
83
+
84
+ source = WebSource(
85
+ title=title[:100] if title else "Unknown",
86
+ url=url,
87
+ snippet=snippet[:300] if snippet else "",
88
+ element=element,
89
+ relevance_score=relevance_score
90
+ )
91
+ self.web_sources.append(source)
92
+ return citation_num
93
+
94
+ def add_knowledge_source(self, knowledge_id: str, goal: str,
95
+ quality_score: float, element: str,
96
+ created_at: str, snippet: str) -> int:
97
+ """์ง€์‹ DB ์ถœ์ฒ˜ ์ถ”๊ฐ€"""
98
+ if knowledge_id in self._citation_map:
99
+ return self._citation_map[knowledge_id]
100
+
101
+ self._citation_counter += 1
102
+ citation_num = self._citation_counter
103
+ self._citation_map[knowledge_id] = citation_num
104
+
105
+ source = KnowledgeSource(
106
+ knowledge_id=knowledge_id,
107
+ goal=goal[:100] if goal else "",
108
+ quality_score=quality_score,
109
+ element=element,
110
+ created_at=created_at,
111
+ snippet=snippet[:200] if snippet else ""
112
+ )
113
+ self.knowledge_sources.append(source)
114
+ return citation_num
115
+
116
+ def add_crawl_source(self, url: str, title: str,
117
+ content_preview: str, crawled_at: str,
118
+ success: bool = True) -> int:
119
+ """ํฌ๋กค๋ง ์ถœ์ฒ˜ ์ถ”๊ฐ€"""
120
+ if url in self._citation_map:
121
+ return self._citation_map[url]
122
+
123
+ self._citation_counter += 1
124
+ citation_num = self._citation_counter
125
+ self._citation_map[url] = citation_num
126
+
127
+ source = CrawlSource(
128
+ url=url,
129
+ title=title[:100] if title else url[:50],
130
+ content_preview=content_preview[:200] if content_preview else "",
131
+ crawled_at=crawled_at,
132
+ success=success
133
+ )
134
+ self.crawl_sources.append(source)
135
+ return citation_num
136
+
137
+ def add_from_search_result(self, search_result: Dict, element: str):
138
+ """Brave ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ์ถœ์ฒ˜ ์ผ๊ด„ ์ถ”๊ฐ€"""
139
+ if not search_result or not search_result.get("success"):
140
+ return
141
+
142
+ for result in search_result.get("results", []):
143
+ self.add_web_source(
144
+ title=result.get("title", ""),
145
+ url=result.get("url", ""),
146
+ snippet=result.get("description", ""),
147
+ element=element
148
+ )
149
+
150
+ # ===== ์ถœ์ฒ˜ ์กฐํšŒ =====
151
+
152
+ def get_citation_number(self, url_or_id: str) -> Optional[int]:
153
+ """URL ๋˜๋Š” ID๋กœ ์ธ์šฉ ๋ฒˆํ˜ธ ์กฐํšŒ"""
154
+ return self._citation_map.get(url_or_id)
155
+
156
+ def get_total_sources(self) -> Dict[str, int]:
157
+ """์ถœ์ฒ˜ ์œ ํ˜•๋ณ„ ๊ฐœ์ˆ˜"""
158
+ return {
159
+ "web": len(self.web_sources),
160
+ "knowledge": len(self.knowledge_sources),
161
+ "crawl": len(self.crawl_sources),
162
+ "total": len(self.web_sources) + len(self.knowledge_sources) + len(self.crawl_sources)
163
+ }
164
+
165
+ def get_sources_by_element(self) -> Dict[str, List]:
166
+ """์˜คํ–‰ ๋‹จ๊ณ„๋ณ„ ์‚ฌ์šฉ๋œ ์ถœ์ฒ˜"""
167
+ by_element = {"ๅœŸ": [], "้‡‘": [], "ๆฐด": [], "ๆœจ": [], "็ซ": []}
168
+
169
+ for src in self.web_sources:
170
+ if src.element in by_element:
171
+ by_element[src.element].append({
172
+ "type": "web",
173
+ "title": src.title,
174
+ "url": src.url
175
+ })
176
+
177
+ for src in self.knowledge_sources:
178
+ if src.element in by_element:
179
+ by_element[src.element].append({
180
+ "type": "knowledge",
181
+ "id": src.knowledge_id,
182
+ "quality": src.quality_score
183
+ })
184
+
185
+ return by_element
186
+
187
+ # ===== ์ถœ๋ ฅ ํฌ๋งทํŒ… =====
188
+
189
+ def format_references_section(self, language: str = "EN") -> str:
190
+ """์ตœ์ข… ๋ณด๊ณ ์„œ์šฉ ์ถœ์ฒ˜ ์„น์…˜ ์ƒ์„ฑ"""
191
+ if language == "KR":
192
+ return self._format_references_kr()
193
+ return self._format_references_en()
194
+
195
+ def _format_references_en(self) -> str:
196
+ """์˜์–ด ์ถœ์ฒ˜ ์„น์…˜"""
197
+ lines = ["## ๐Ÿ“š Sources & References", ""]
198
+
199
+ total = self.get_total_sources()
200
+ if total["total"] == 0:
201
+ lines.append("*No external sources used in this analysis.*")
202
+ return "\n".join(lines)
203
+
204
+ # ์š”์•ฝ ํ†ต๊ณ„
205
+ lines.append(f"**Total Sources**: {total['total']} "
206
+ f"(Web: {total['web']}, Knowledge DB: {total['knowledge']}, Crawl: {total['crawl']})")
207
+ lines.append("")
208
+
209
+ # ์›น ์ถœ์ฒ˜
210
+ if self.web_sources:
211
+ lines.append("### ๐ŸŒ Web Sources")
212
+ lines.append("| # | Title | Source | Element |")
213
+ lines.append("|---|-------|--------|---------|")
214
+ for i, src in enumerate(self.web_sources[:10], 1):
215
+ domain = self._extract_domain(src.url)
216
+ lines.append(f"| [{i}] | {src.title[:40]}{'...' if len(src.title) > 40 else ''} | [{domain}]({src.url}) | {src.element} |")
217
+ if len(self.web_sources) > 10:
218
+ lines.append(f"| ... | *+{len(self.web_sources) - 10} more sources* | | |")
219
+ lines.append("")
220
+
221
+ # ์ง€์‹ DB ์ถœ์ฒ˜
222
+ if self.knowledge_sources:
223
+ lines.append("### ๐Ÿง  Knowledge Database")
224
+ lines.append("| # | Related Goal | Quality | Element |")
225
+ lines.append("|---|--------------|---------|---------|")
226
+ for i, src in enumerate(self.knowledge_sources[:5], 1):
227
+ quality_bar = 'โ–ˆ' * int(src.quality_score * 5) + 'โ–‘' * (5 - int(src.quality_score * 5))
228
+ lines.append(f"| [K{i}] | {src.goal[:35]}{'...' if len(src.goal) > 35 else ''} | {quality_bar} {src.quality_score:.0%} | {src.element} |")
229
+ lines.append("")
230
+
231
+ # ํฌ๋กค๋ง ์ถœ์ฒ˜
232
+ if self.crawl_sources:
233
+ lines.append("### ๐Ÿ”— Crawled Pages")
234
+ for i, src in enumerate(self.crawl_sources[:5], 1):
235
+ status = "โœ…" if src.success else "โš ๏ธ"
236
+ lines.append(f"- {status} [{src.title[:50]}]({src.url})")
237
+ lines.append("")
238
+
239
+ return "\n".join(lines)
240
+
241
+ def _format_references_kr(self) -> str:
242
+ """ํ•œ๊ตญ์–ด ์ถœ์ฒ˜ ์„น์…˜"""
243
+ lines = ["## ๐Ÿ“š ์ถœ์ฒ˜ ๋ฐ ์ฐธ๊ณ ์ž๋ฃŒ", ""]
244
+
245
+ total = self.get_total_sources()
246
+ if total["total"] == 0:
247
+ lines.append("*์ด ๋ถ„์„์— ์‚ฌ์šฉ๋œ ์™ธ๋ถ€ ์ถœ์ฒ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.*")
248
+ return "\n".join(lines)
249
+
250
+ # ์š”์•ฝ ํ†ต๊ณ„
251
+ lines.append(f"**์ด ์ถœ์ฒ˜**: {total['total']}๊ฑด "
252
+ f"(์›น: {total['web']}, ์ง€์‹DB: {total['knowledge']}, ํฌ๋กค๋ง: {total['crawl']})")
253
+ lines.append("")
254
+
255
+ # ์›น ์ถœ์ฒ˜
256
+ if self.web_sources:
257
+ lines.append("### ๐ŸŒ ์›น ๊ฒ€์ƒ‰ ์ถœ์ฒ˜")
258
+ lines.append("| # | ์ œ๋ชฉ | ์ถœ์ฒ˜ | ๋‹จ๊ณ„ |")
259
+ lines.append("|---|------|------|------|")
260
+ for i, src in enumerate(self.web_sources[:10], 1):
261
+ domain = self._extract_domain(src.url)
262
+ element_name = {"ๅœŸ": "๊ฐ๋…", "้‡‘": "๋น„ํ‰", "ๆฐด": "๋ฆฌ์„œ์น˜", "ๆœจ": "์ฐฝ๋ฐœ", "็ซ": "์‹คํ–‰"}.get(src.element, src.element)
263
+ lines.append(f"| [{i}] | {src.title[:40]}{'...' if len(src.title) > 40 else ''} | [{domain}]({src.url}) | {src.element}({element_name}) |")
264
+ if len(self.web_sources) > 10:
265
+ lines.append(f"| ... | *+{len(self.web_sources) - 10}๊ฑด ์ถ”๊ฐ€* | | |")
266
+ lines.append("")
267
+
268
+ # ์ง€์‹ DB ์ถœ์ฒ˜
269
+ if self.knowledge_sources:
270
+ lines.append("### ๐Ÿง  ์ง€์‹ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค")
271
+ lines.append("| # | ๊ด€๋ จ ๋ชฉํ‘œ | ํ’ˆ์งˆ | ๋‹จ๊ณ„ |")
272
+ lines.append("|---|----------|------|------|")
273
+ for i, src in enumerate(self.knowledge_sources[:5], 1):
274
+ quality_bar = 'โ–ˆ' * int(src.quality_score * 5) + 'โ–‘' * (5 - int(src.quality_score * 5))
275
+ lines.append(f"| [K{i}] | {src.goal[:35]}{'...' if len(src.goal) > 35 else ''} | {quality_bar} {src.quality_score:.0%} | {src.element} |")
276
+ lines.append("")
277
+
278
+ # ํฌ๋กค๋ง ์ถœ์ฒ˜
279
+ if self.crawl_sources:
280
+ lines.append("### ๐Ÿ”— ํฌ๋กค๋ง ํŽ˜์ด์ง€")
281
+ for i, src in enumerate(self.crawl_sources[:5], 1):
282
+ status = "โœ…" if src.success else "โš ๏ธ"
283
+ lines.append(f"- {status} [{src.title[:50]}]({src.url})")
284
+ lines.append("")
285
+
286
+ return "\n".join(lines)
287
+
288
+ def format_inline_citation(self, url_or_id: str) -> str:
289
+ """๋ณธ๋ฌธ ๋‚ด ์ธ๋ผ์ธ ์ธ์šฉ ํ˜•์‹ ์ƒ์„ฑ [1], [K2] ๋“ฑ"""
290
+ num = self._citation_map.get(url_or_id)
291
+ if num is None:
292
+ return ""
293
+
294
+ # ์ง€์‹ DB ์ถœ์ฒ˜์ธ์ง€ ํ™•์ธ
295
+ for src in self.knowledge_sources:
296
+ if src.knowledge_id == url_or_id:
297
+ return f"[K{num}]"
298
+
299
+ return f"[{num}]"
300
+
301
+ def _extract_domain(self, url: str) -> str:
302
+ """URL์—์„œ ๋„๋ฉ”์ธ ์ถ”์ถœ"""
303
+ try:
304
+ from urllib.parse import urlparse
305
+ parsed = urlparse(url)
306
+ domain = parsed.netloc
307
+ # www. ์ œ๊ฑฐ
308
+ if domain.startswith("www."):
309
+ domain = domain[4:]
310
+ return domain[:30]
311
+ except:
312
+ return url[:30]
313
+
314
+ def to_dict(self) -> Dict:
315
+ """์ง๋ ฌํ™”์šฉ ๋”•์…”๋„ˆ๋ฆฌ ๋ณ€ํ™˜"""
316
+ return {
317
+ "web_sources": [
318
+ {"title": s.title, "url": s.url, "element": s.element, "snippet": s.snippet}
319
+ for s in self.web_sources
320
+ ],
321
+ "knowledge_sources": [
322
+ {"id": s.knowledge_id, "goal": s.goal, "quality": s.quality_score, "element": s.element}
323
+ for s in self.knowledge_sources
324
+ ],
325
+ "crawl_sources": [
326
+ {"url": s.url, "title": s.title, "success": s.success}
327
+ for s in self.crawl_sources
328
+ ],
329
+ "total": self.get_total_sources()
330
+ }
331
+
332
+
333
+ # ==================== TypedOutputFormatter (P1-1) ====================
334
+
335
+ class TypedOutputFormatter:
336
+ """
337
+ ์งˆ๋ฌธ ์œ ํ˜•๋ณ„ ๋งž์ถค ์ถœ๋ ฅ ํฌ๋งทํ„ฐ
338
+
339
+ ์˜ˆ์ธก, ๋น„๊ต, ์ „๋žต, ๋ถ„์„, ๋ฐœ๋ช…, ์Šคํ† ๋ฆฌ, ๋ ˆ์‹œํ”ผ ๋“ฑ
340
+ ๊ฐ ์œ ํ˜•์— ์ตœ์ ํ™”๋œ ์ถœ๋ ฅ ๊ตฌ์กฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
341
+ """
342
+
343
+ # ์งˆ๋ฌธ ์œ ํ˜•๋ณ„ ์•„์ด์ฝ˜
344
+ TYPE_ICONS = {
345
+ "prediction": "๐Ÿ”ฎ",
346
+ "comparison": "โš–๏ธ",
347
+ "strategy": "๐ŸŽฏ",
348
+ "analysis": "๐Ÿ”ฌ",
349
+ "invention": "๐Ÿ’ก",
350
+ "story": "๐Ÿ“–",
351
+ "recipe": "๐Ÿณ",
352
+ "general": "๐Ÿ“‹"
353
+ }
354
+
355
+ # ํ•œ๊ตญ์–ด ์œ ํ˜•๋ช…
356
+ TYPE_NAMES_KR = {
357
+ "prediction": "์˜ˆ์ธก ๋ถ„์„",
358
+ "comparison": "๋น„๊ต ๋ถ„์„",
359
+ "strategy": "์ „๋žต ์ˆ˜๋ฆฝ",
360
+ "analysis": "์‹ฌ์ธต ๋ถ„์„",
361
+ "invention": "๋ฐœ๋ช…/ํŠนํ—ˆ",
362
+ "story": "์Šคํ† ๋ฆฌ/์ฐฝ์ž‘",
363
+ "recipe": "๋ ˆ์‹œํ”ผ/์š”๋ฆฌ",
364
+ "general": "์ผ๋ฐ˜ ๋ถ„์„"
365
+ }
366
+
367
+ TYPE_NAMES_EN = {
368
+ "prediction": "Prediction Analysis",
369
+ "comparison": "Comparative Analysis",
370
+ "strategy": "Strategic Planning",
371
+ "analysis": "In-depth Analysis",
372
+ "invention": "Invention/Patent",
373
+ "story": "Story/Creative",
374
+ "recipe": "Recipe/Cooking",
375
+ "general": "General Analysis"
376
+ }
377
+
378
+ @classmethod
379
+ def format(cls, question_type: str, data: Dict, language: str = "EN") -> str:
380
+ """์งˆ๋ฌธ ์œ ํ˜•์— ๋งž๋Š” ํฌ๋งท ์ ์šฉ"""
381
+ formatters = {
382
+ "prediction": cls.format_prediction,
383
+ "comparison": cls.format_comparison,
384
+ "strategy": cls.format_strategy,
385
+ "analysis": cls.format_analysis,
386
+ "invention": cls.format_invention,
387
+ "story": cls.format_story,
388
+ "recipe": cls.format_recipe,
389
+ "general": cls.format_general
390
+ }
391
+
392
+ formatter = formatters.get(question_type, cls.format_general)
393
+ return formatter(data, language)
394
+
395
+ @classmethod
396
+ def get_type_header(cls, question_type: str, language: str = "EN") -> str:
397
+ """์งˆ๋ฌธ ์œ ํ˜• ํ—ค๋” ์ƒ์„ฑ"""
398
+ icon = cls.TYPE_ICONS.get(question_type, "๐Ÿ“‹")
399
+ if language == "KR":
400
+ name = cls.TYPE_NAMES_KR.get(question_type, "์ผ๋ฐ˜ ๋ถ„์„")
401
+ return f"{icon} **๋ถ„์„ ์œ ํ˜•**: {name}"
402
+ else:
403
+ name = cls.TYPE_NAMES_EN.get(question_type, "General Analysis")
404
+ return f"{icon} **Analysis Type**: {name}"
405
+
406
+ # ===== ์˜ˆ์ธก ๋ถ„์„ ํฌ๋งท =====
407
+
408
+ @classmethod
409
+ def format_prediction(cls, data: Dict, language: str = "EN") -> str:
410
+ """์˜ˆ์ธก ๋ถ„์„์šฉ ํฌ๋งท - ์‹œ๋‚˜๋ฆฌ์˜ค, ํ™•๋ฅ , ๋ณ€์ˆ˜"""
411
+ if language == "KR":
412
+ return cls._format_prediction_kr(data)
413
+ return cls._format_prediction_en(data)
414
+
415
+ @classmethod
416
+ def _format_prediction_en(cls, data: Dict) -> str:
417
+ lines = ["### ๐Ÿ”ฎ Prediction Framework", ""]
418
+
419
+ # ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ด๋ธ”
420
+ scenarios = data.get("scenarios", [])
421
+ if scenarios:
422
+ lines.append("#### Scenario Analysis")
423
+ lines.append("| Scenario | Probability | Key Drivers | Timeline |")
424
+ lines.append("|----------|-------------|-------------|----------|")
425
+ for i, s in enumerate(scenarios[:4], 1):
426
+ prob = s.get("probability", "?")
427
+ drivers = s.get("drivers", "-")[:30]
428
+ timeline = s.get("timeline", "-")
429
+ lines.append(f"| **{s.get('name', f'Scenario {i}')}** | {prob} | {drivers} | {timeline} |")
430
+ lines.append("")
431
+
432
+ # ํ•ต์‹ฌ ๋ณ€์ˆ˜
433
+ variables = data.get("key_variables", [])
434
+ if variables:
435
+ lines.append("#### Key Variables & Uncertainties")
436
+ for v in variables[:5]:
437
+ impact = v.get("impact", "medium")
438
+ icon = "๐Ÿ”ด" if impact == "high" else "๐ŸŸก" if impact == "medium" else "๐ŸŸข"
439
+ lines.append(f"- {icon} **{v.get('name', 'Variable')}**: {v.get('description', '')[:60]}")
440
+ lines.append("")
441
+
442
+ # ์˜ˆ์ธก ์‹ ๋ขฐ๋„
443
+ confidence = data.get("prediction_confidence", 0)
444
+ if confidence:
445
+ bar = 'โ–ˆ' * int(confidence * 10) + 'โ–‘' * (10 - int(confidence * 10))
446
+ lines.append(f"**Prediction Confidence**: {bar} {confidence:.0%}")
447
+ lines.append("")
448
+
449
+ return "\n".join(lines)
450
+
451
+ @classmethod
452
+ def _format_prediction_kr(cls, data: Dict) -> str:
453
+ lines = ["### ๐Ÿ”ฎ ์˜ˆ์ธก ํ”„๋ ˆ์ž„์›Œํฌ", ""]
454
+
455
+ # ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ด๋ธ”
456
+ scenarios = data.get("scenarios", [])
457
+ if scenarios:
458
+ lines.append("#### ์‹œ๋‚˜๋ฆฌ์˜ค ๋ถ„์„")
459
+ lines.append("| ์‹œ๋‚˜๋ฆฌ์˜ค | ํ™•๋ฅ  | ํ•ต์‹ฌ ๋™์ธ | ์‹œ์  |")
460
+ lines.append("|----------|------|----------|------|")
461
+ for i, s in enumerate(scenarios[:4], 1):
462
+ prob = s.get("probability", "?")
463
+ drivers = s.get("drivers", "-")[:30]
464
+ timeline = s.get("timeline", "-")
465
+ lines.append(f"| **{s.get('name', f'์‹œ๋‚˜๋ฆฌ์˜ค {i}')}** | {prob} | {drivers} | {timeline} |")
466
+ lines.append("")
467
+
468
+ # ํ•ต์‹ฌ ๋ณ€์ˆ˜
469
+ variables = data.get("key_variables", [])
470
+ if variables:
471
+ lines.append("#### ํ•ต์‹ฌ ๋ณ€์ˆ˜ ๋ฐ ๋ถˆํ™•์‹ค์„ฑ")
472
+ for v in variables[:5]:
473
+ impact = v.get("impact", "medium")
474
+ icon = "๐Ÿ”ด" if impact == "high" else "๐ŸŸก" if impact == "medium" else "๐ŸŸข"
475
+ lines.append(f"- {icon} **{v.get('name', '๋ณ€์ˆ˜')}**: {v.get('description', '')[:60]}")
476
+ lines.append("")
477
+
478
+ # ์˜ˆ์ธก ์‹ ๋ขฐ๋„
479
+ confidence = data.get("prediction_confidence", 0)
480
+ if confidence:
481
+ bar = 'โ–ˆ' * int(confidence * 10) + 'โ–‘' * (10 - int(confidence * 10))
482
+ lines.append(f"**์˜ˆ์ธก ์‹ ๋ขฐ๋„**: {bar} {confidence:.0%}")
483
+ lines.append("")
484
+
485
+ return "\n".join(lines)
486
+
487
+ # ===== ๋น„๊ต ๋ถ„์„ ํฌ๋งท =====
488
+
489
+ @classmethod
490
+ def format_comparison(cls, data: Dict, language: str = "EN") -> str:
491
+ """๋น„๊ต ๋ถ„์„์šฉ ํฌ๋งท - ๋งคํŠธ๋ฆญ์Šค, ์žฅ๋‹จ์ , ๊ถŒ์žฅ"""
492
+ if language == "KR":
493
+ return cls._format_comparison_kr(data)
494
+ return cls._format_comparison_en(data)
495
+
496
+ @classmethod
497
+ def _format_comparison_en(cls, data: Dict) -> str:
498
+ lines = ["### โš–๏ธ Comparison Matrix", ""]
499
+
500
+ # ๋น„๊ต ๋Œ€์ƒ
501
+ items = data.get("comparison_items", [])
502
+ criteria = data.get("criteria", [])
503
+
504
+ if items and criteria:
505
+ # ํ—ค๋” ์ƒ์„ฑ
506
+ header = "| Criteria |"
507
+ separator = "|----------|"
508
+ for item in items[:4]:
509
+ header += f" {item.get('name', 'Option')[:15]} |"
510
+ separator += "--------|"
511
+ lines.append(header)
512
+ lines.append(separator)
513
+
514
+ # ๊ฐ ๊ธฐ์ค€๋ณ„ ๋น„๊ต
515
+ for crit in criteria[:6]:
516
+ row = f"| **{crit.get('name', 'Criterion')[:20]}** |"
517
+ for item in items[:4]:
518
+ score = item.get("scores", {}).get(crit.get("name", ""), "-")
519
+ if isinstance(score, (int, float)):
520
+ score = f"{'โญ' * int(score)}" if score <= 5 else str(score)
521
+ row += f" {str(score)[:10]} |"
522
+ lines.append(row)
523
+ lines.append("")
524
+
525
+ # ์žฅ๋‹จ์ 
526
+ pros_cons = data.get("pros_cons", {})
527
+ if pros_cons:
528
+ lines.append("#### Pros & Cons Summary")
529
+ for item_name, pc in pros_cons.items():
530
+ lines.append(f"**{item_name}**")
531
+ if pc.get("pros"):
532
+ lines.append(f" - โœ… Pros: {', '.join(pc['pros'][:3])}")
533
+ if pc.get("cons"):
534
+ lines.append(f" - โŒ Cons: {', '.join(pc['cons'][:3])}")
535
+ lines.append("")
536
+
537
+ # ๊ถŒ์žฅ์‚ฌํ•ญ
538
+ recommendation = data.get("recommendation", "")
539
+ if recommendation:
540
+ lines.append(f"#### ๐Ÿ’ก Recommendation")
541
+ lines.append(f"> {recommendation}")
542
+ lines.append("")
543
+
544
+ return "\n".join(lines)
545
+
546
+ @classmethod
547
+ def _format_comparison_kr(cls, data: Dict) -> str:
548
+ lines = ["### โš–๏ธ ๋น„๊ต ๋งคํŠธ๋ฆญ์Šค", ""]
549
+
550
+ # ๋น„๊ต ๋Œ€์ƒ
551
+ items = data.get("comparison_items", [])
552
+ criteria = data.get("criteria", [])
553
+
554
+ if items and criteria:
555
+ header = "| ๊ธฐ์ค€ |"
556
+ separator = "|------|"
557
+ for item in items[:4]:
558
+ header += f" {item.get('name', '์˜ต์…˜')[:15]} |"
559
+ separator += "--------|"
560
+ lines.append(header)
561
+ lines.append(separator)
562
+
563
+ for crit in criteria[:6]:
564
+ row = f"| **{crit.get('name', '๊ธฐ์ค€')[:20]}** |"
565
+ for item in items[:4]:
566
+ score = item.get("scores", {}).get(crit.get("name", ""), "-")
567
+ if isinstance(score, (int, float)):
568
+ score = f"{'โญ' * int(score)}" if score <= 5 else str(score)
569
+ row += f" {str(score)[:10]} |"
570
+ lines.append(row)
571
+ lines.append("")
572
+
573
+ # ์žฅ๋‹จ์ 
574
+ pros_cons = data.get("pros_cons", {})
575
+ if pros_cons:
576
+ lines.append("#### ์žฅ๋‹จ์  ์š”์•ฝ")
577
+ for item_name, pc in pros_cons.items():
578
+ lines.append(f"**{item_name}**")
579
+ if pc.get("pros"):
580
+ lines.append(f" - โœ… ์žฅ์ : {', '.join(pc['pros'][:3])}")
581
+ if pc.get("cons"):
582
+ lines.append(f" - โŒ ๋‹จ์ : {', '.join(pc['cons'][:3])}")
583
+ lines.append("")
584
+
585
+ # ๊ถŒ์žฅ์‚ฌํ•ญ
586
+ recommendation = data.get("recommendation", "")
587
+ if recommendation:
588
+ lines.append(f"#### ๐Ÿ’ก ๊ถŒ์žฅ์‚ฌํ•ญ")
589
+ lines.append(f"> {recommendation}")
590
+ lines.append("")
591
+
592
+ return "\n".join(lines)
593
+
594
+ # ===== ์ „๋žต ๋ถ„์„ ํฌ๋งท =====
595
+
596
+ @classmethod
597
+ def format_strategy(cls, data: Dict, language: str = "EN") -> str:
598
+ """์ „๋žต ๋ถ„์„์šฉ ํฌ๋งท - ๋ชฉํ‘œ, ๋กœ๋“œ๋งต, KPI"""
599
+ if language == "KR":
600
+ return cls._format_strategy_kr(data)
601
+ return cls._format_strategy_en(data)
602
+
603
+ @classmethod
604
+ def _format_strategy_en(cls, data: Dict) -> str:
605
+ lines = ["### ๐ŸŽฏ Strategic Framework", ""]
606
+
607
+ # ์ „๋žต ๋ชฉํ‘œ
608
+ objectives = data.get("objectives", [])
609
+ if objectives:
610
+ lines.append("#### Strategic Objectives")
611
+ for i, obj in enumerate(objectives[:5], 1):
612
+ lines.append(f"{i}. **{obj.get('name', 'Objective')}**: {obj.get('description', '')[:60]}")
613
+ lines.append("")
614
+
615
+ # ์‹คํ–‰ ๋กœ๋“œ๋งต
616
+ roadmap = data.get("roadmap", [])
617
+ if roadmap:
618
+ lines.append("#### Implementation Roadmap")
619
+ lines.append("| Phase | Timeline | Key Actions | Deliverables |")
620
+ lines.append("|-------|----------|-------------|--------------|")
621
+ for phase in roadmap[:5]:
622
+ actions = phase.get("actions", ["-"])
623
+ deliverables = phase.get("deliverables", ["-"])
624
+ lines.append(f"| {phase.get('name', 'Phase')} | {phase.get('timeline', '-')} | {', '.join(actions[:2])} | {', '.join(deliverables[:2])} |")
625
+ lines.append("")
626
+
627
+ # KPI
628
+ kpis = data.get("kpis", [])
629
+ if kpis:
630
+ lines.append("#### Key Performance Indicators")
631
+ for kpi in kpis[:5]:
632
+ target = kpi.get("target", "TBD")
633
+ lines.append(f"- ๐Ÿ“Š **{kpi.get('name', 'KPI')}**: Target {target}")
634
+ lines.append("")
635
+
636
+ return "\n".join(lines)
637
+
638
+ @classmethod
639
+ def _format_strategy_kr(cls, data: Dict) -> str:
640
+ lines = ["### ๐ŸŽฏ ์ „๋žต ํ”„๋ ˆ์ž„์›Œํฌ", ""]
641
+
642
+ # ์ „๋žต ๋ชฉํ‘œ
643
+ objectives = data.get("objectives", [])
644
+ if objectives:
645
+ lines.append("#### ์ „๋žต ๋ชฉํ‘œ")
646
+ for i, obj in enumerate(objectives[:5], 1):
647
+ lines.append(f"{i}. **{obj.get('name', '๋ชฉํ‘œ')}**: {obj.get('description', '')[:60]}")
648
+ lines.append("")
649
+
650
+ # ์‹คํ–‰ ๋กœ๋“œ๋งต
651
+ roadmap = data.get("roadmap", [])
652
+ if roadmap:
653
+ lines.append("#### ์‹คํ–‰ ๋กœ๋“œ๋งต")
654
+ lines.append("| ๋‹จ๊ณ„ | ๊ธฐ๊ฐ„ | ์ฃผ์š” ํ™œ๋™ | ์‚ฐ์ถœ๋ฌผ |")
655
+ lines.append("|------|------|----------|--------|")
656
+ for phase in roadmap[:5]:
657
+ actions = phase.get("actions", ["-"])
658
+ deliverables = phase.get("deliverables", ["-"])
659
+ lines.append(f"| {phase.get('name', '๋‹จ๊ณ„')} | {phase.get('timeline', '-')} | {', '.join(actions[:2])} | {', '.join(deliverables[:2])} |")
660
+ lines.append("")
661
+
662
+ # KPI
663
+ kpis = data.get("kpis", [])
664
+ if kpis:
665
+ lines.append("#### ํ•ต์‹ฌ ์„ฑ๊ณผ ์ง€ํ‘œ (KPI)")
666
+ for kpi in kpis[:5]:
667
+ target = kpi.get("target", "TBD")
668
+ lines.append(f"- ๐Ÿ“Š **{kpi.get('name', 'KPI')}**: ๋ชฉํ‘œ {target}")
669
+ lines.append("")
670
+
671
+ return "\n".join(lines)
672
+
673
+ # ===== ์ผ๋ฐ˜ ๋ถ„์„ ํฌ๋งท =====
674
+
675
+ @classmethod
676
+ def format_analysis(cls, data: Dict, language: str = "EN") -> str:
677
+ """์ผ๋ฐ˜ ๋ถ„์„์šฉ ํฌ๋งท"""
678
+ if language == "KR":
679
+ return cls._format_analysis_kr(data)
680
+ return cls._format_analysis_en(data)
681
+
682
+ @classmethod
683
+ def _format_analysis_en(cls, data: Dict) -> str:
684
+ lines = ["### ๐Ÿ”ฌ Analysis Framework", ""]
685
+
686
+ # ์ฃผ์š” ๋ฐœ๊ฒฌ
687
+ findings = data.get("findings", [])
688
+ if findings:
689
+ lines.append("#### Key Findings")
690
+ for i, f in enumerate(findings[:5], 1):
691
+ lines.append(f"{i}. {f}")
692
+ lines.append("")
693
+
694
+ # ์›์ธ ๋ถ„์„
695
+ causes = data.get("causes", [])
696
+ if causes:
697
+ lines.append("#### Root Cause Analysis")
698
+ for c in causes[:4]:
699
+ lines.append(f"- **{c.get('cause', 'Cause')}** โ†’ {c.get('effect', 'Effect')[:50]}")
700
+ lines.append("")
701
+
702
+ # ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ
703
+ data_points = data.get("data_points", [])
704
+ if data_points:
705
+ lines.append("#### Supporting Data")
706
+ for dp in data_points[:5]:
707
+ lines.append(f"- ๐Ÿ“ˆ {dp}")
708
+ lines.append("")
709
+
710
+ return "\n".join(lines)
711
+
712
+ @classmethod
713
+ def _format_analysis_kr(cls, data: Dict) -> str:
714
+ lines = ["### ๐Ÿ”ฌ ๋ถ„์„ ํ”„๋ ˆ์ž„์›Œํฌ", ""]
715
+
716
+ # ์ฃผ์š” ๋ฐœ๊ฒฌ
717
+ findings = data.get("findings", [])
718
+ if findings:
719
+ lines.append("#### ์ฃผ์š” ๋ฐœ๊ฒฌ์‚ฌํ•ญ")
720
+ for i, f in enumerate(findings[:5], 1):
721
+ lines.append(f"{i}. {f}")
722
+ lines.append("")
723
+
724
+ # ์›์ธ ๋ถ„์„
725
+ causes = data.get("causes", [])
726
+ if causes:
727
+ lines.append("#### ์›์ธ ๋ถ„์„")
728
+ for c in causes[:4]:
729
+ lines.append(f"- **{c.get('cause', '์›์ธ')}** โ†’ {c.get('effect', '๊ฒฐ๊ณผ')[:50]}")
730
+ lines.append("")
731
+
732
+ # ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ
733
+ data_points = data.get("data_points", [])
734
+ if data_points:
735
+ lines.append("#### ๊ทผ๊ฑฐ ๋ฐ์ดํ„ฐ")
736
+ for dp in data_points[:5]:
737
+ lines.append(f"- ๐Ÿ“ˆ {dp}")
738
+ lines.append("")
739
+
740
+ return "\n".join(lines)
741
+
742
+ # ===== ๋ฐœ๋ช…/ํŠนํ—ˆ ํฌ๋งท =====
743
+
744
+ @classmethod
745
+ def format_invention(cls, data: Dict, language: str = "EN") -> str:
746
+ """๋ฐœ๋ช…/ํŠนํ—ˆ์šฉ ํฌ๋งท"""
747
+ if language == "KR":
748
+ lines = ["### ๐Ÿ’ก ๋ฐœ๋ช… ๊ฐœ์š”", ""]
749
+
750
+ if data.get("invention_name"):
751
+ lines.append(f"**๋ฐœ๋ช…์˜ ๋ช…์นญ**: {data['invention_name']}")
752
+ lines.append("")
753
+
754
+ if data.get("problem"):
755
+ lines.append("#### ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” ๊ณผ์ œ")
756
+ lines.append(data["problem"])
757
+ lines.append("")
758
+
759
+ if data.get("solution"):
760
+ lines.append("#### ๊ณผ์ œ ํ•ด๊ฒฐ ์ˆ˜๋‹จ")
761
+ lines.append(data["solution"])
762
+ lines.append("")
763
+
764
+ claims = data.get("claims", [])
765
+ if claims:
766
+ lines.append("#### ์ฒญ๊ตฌํ•ญ ์ดˆ์•ˆ")
767
+ for i, claim in enumerate(claims[:5], 1):
768
+ lines.append(f"**์ฒญ๊ตฌํ•ญ {i}**: {claim}")
769
+ lines.append("")
770
+
771
+ if data.get("advantages"):
772
+ lines.append("#### ๋ฐœ๋ช…์˜ ํšจ๊ณผ")
773
+ for adv in data["advantages"][:5]:
774
+ lines.append(f"- {adv}")
775
+ lines.append("")
776
+ else:
777
+ lines = ["### ๐Ÿ’ก Invention Overview", ""]
778
+
779
+ if data.get("invention_name"):
780
+ lines.append(f"**Title**: {data['invention_name']}")
781
+ lines.append("")
782
+
783
+ if data.get("problem"):
784
+ lines.append("#### Problem to Solve")
785
+ lines.append(data["problem"])
786
+ lines.append("")
787
+
788
+ if data.get("solution"):
789
+ lines.append("#### Solution")
790
+ lines.append(data["solution"])
791
+ lines.append("")
792
+
793
+ claims = data.get("claims", [])
794
+ if claims:
795
+ lines.append("#### Draft Claims")
796
+ for i, claim in enumerate(claims[:5], 1):
797
+ lines.append(f"**Claim {i}**: {claim}")
798
+ lines.append("")
799
+
800
+ if data.get("advantages"):
801
+ lines.append("#### Advantages")
802
+ for adv in data["advantages"][:5]:
803
+ lines.append(f"- {adv}")
804
+ lines.append("")
805
+
806
+ return "\n".join(lines)
807
+
808
+ # ===== ์Šคํ† ๋ฆฌ/์ฐฝ์ž‘ ํฌ๋งท =====
809
+
810
+ @classmethod
811
+ def format_story(cls, data: Dict, language: str = "EN") -> str:
812
+ """์Šคํ† ๋ฆฌ/์ฐฝ์ž‘์šฉ ํฌ๋งท"""
813
+ if language == "KR":
814
+ lines = ["### ๐Ÿ“– ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ", ""]
815
+
816
+ if data.get("logline"):
817
+ lines.append(f"**๋กœ๊ทธ๋ผ์ธ**: {data['logline']}")
818
+ lines.append("")
819
+
820
+ characters = data.get("characters", [])
821
+ if characters:
822
+ lines.append("#### ๋“ฑ์žฅ์ธ๋ฌผ")
823
+ for char in characters[:5]:
824
+ lines.append(f"- **{char.get('name', '์บ๋ฆญํ„ฐ')}**: {char.get('description', '')[:60]}")
825
+ lines.append("")
826
+
827
+ structure = data.get("structure", {})
828
+ if structure:
829
+ lines.append("#### ํ”Œ๋กฏ ๊ตฌ์กฐ")
830
+ for key, value in [("setup", "๋ฐœ๋‹จ"), ("conflict", "๊ฐˆ๋“ฑ"), ("climax", "์ ˆ์ •"), ("resolution", "๊ฒฐ๋ง")]:
831
+ if structure.get(key):
832
+ lines.append(f"- **{value}**: {structure[key][:80]}")
833
+ lines.append("")
834
+
835
+ if data.get("theme"):
836
+ lines.append(f"#### ์ฃผ์ œ")
837
+ lines.append(data["theme"])
838
+ lines.append("")
839
+ else:
840
+ lines = ["### ๐Ÿ“– Story Structure", ""]
841
+
842
+ if data.get("logline"):
843
+ lines.append(f"**Logline**: {data['logline']}")
844
+ lines.append("")
845
+
846
+ characters = data.get("characters", [])
847
+ if characters:
848
+ lines.append("#### Characters")
849
+ for char in characters[:5]:
850
+ lines.append(f"- **{char.get('name', 'Character')}**: {char.get('description', '')[:60]}")
851
+ lines.append("")
852
+
853
+ structure = data.get("structure", {})
854
+ if structure:
855
+ lines.append("#### Plot Structure")
856
+ for key in ["setup", "conflict", "climax", "resolution"]:
857
+ if structure.get(key):
858
+ lines.append(f"- **{key.capitalize()}**: {structure[key][:80]}")
859
+ lines.append("")
860
+
861
+ if data.get("theme"):
862
+ lines.append(f"#### Theme")
863
+ lines.append(data["theme"])
864
+ lines.append("")
865
+
866
+ return "\n".join(lines)
867
+
868
+ # ===== ๋ ˆ์‹œํ”ผ ํฌ๋งท =====
869
+
870
+ @classmethod
871
+ def format_recipe(cls, data: Dict, language: str = "EN") -> str:
872
+ """๋ ˆ์‹œํ”ผ์šฉ ํฌ๋งท"""
873
+ if language == "KR":
874
+ lines = ["### ๐Ÿณ ๋ ˆ์‹œํ”ผ", ""]
875
+
876
+ if data.get("dish_name"):
877
+ lines.append(f"**์š”๋ฆฌ๋ช…**: {data['dish_name']}")
878
+ if data.get("servings"):
879
+ lines.append(f"**๋ถ„๋Ÿ‰**: {data['servings']}")
880
+ if data.get("time"):
881
+ lines.append(f"**์กฐ๋ฆฌ์‹œ๊ฐ„**: {data['time']}")
882
+ lines.append("")
883
+
884
+ ingredients = data.get("ingredients", [])
885
+ if ingredients:
886
+ lines.append("#### ์žฌ๋ฃŒ")
887
+ for ing in ingredients:
888
+ lines.append(f"- {ing}")
889
+ lines.append("")
890
+
891
+ steps = data.get("steps", [])
892
+ if steps:
893
+ lines.append("#### ์กฐ๋ฆฌ ์ˆœ์„œ")
894
+ for i, step in enumerate(steps, 1):
895
+ lines.append(f"{i}. {step}")
896
+ lines.append("")
897
+
898
+ tips = data.get("tips", [])
899
+ if tips:
900
+ lines.append("#### ๐Ÿ’ก ์š”๋ฆฌ ํŒ")
901
+ for tip in tips[:3]:
902
+ lines.append(f"- {tip}")
903
+ lines.append("")
904
+ else:
905
+ lines = ["### ๐Ÿณ Recipe", ""]
906
+
907
+ if data.get("dish_name"):
908
+ lines.append(f"**Dish**: {data['dish_name']}")
909
+ if data.get("servings"):
910
+ lines.append(f"**Servings**: {data['servings']}")
911
+ if data.get("time"):
912
+ lines.append(f"**Time**: {data['time']}")
913
+ lines.append("")
914
+
915
+ ingredients = data.get("ingredients", [])
916
+ if ingredients:
917
+ lines.append("#### Ingredients")
918
+ for ing in ingredients:
919
+ lines.append(f"- {ing}")
920
+ lines.append("")
921
+
922
+ steps = data.get("steps", [])
923
+ if steps:
924
+ lines.append("#### Instructions")
925
+ for i, step in enumerate(steps, 1):
926
+ lines.append(f"{i}. {step}")
927
+ lines.append("")
928
+
929
+ tips = data.get("tips", [])
930
+ if tips:
931
+ lines.append("#### ๐Ÿ’ก Tips")
932
+ for tip in tips[:3]:
933
+ lines.append(f"- {tip}")
934
+ lines.append("")
935
+
936
+ return "\n".join(lines)
937
+
938
+ # ===== ์ผ๋ฐ˜ ํฌ๋งท =====
939
+
940
+ @classmethod
941
+ def format_general(cls, data: Dict, language: str = "EN") -> str:
942
+ """์ผ๋ฐ˜ ๋ถ„์„์šฉ ๊ธฐ๋ณธ ํฌ๋งท"""
943
+ lines = []
944
+
945
+ # ์ฃผ์š” ๋‚ด์šฉ์ด ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ
946
+ main_content = data.get("main_content", data.get("result", ""))
947
+ if main_content:
948
+ lines.append(main_content)
949
+ lines.append("")
950
+
951
+ # ํ•ต์‹ฌ ํฌ์ธํŠธ
952
+ key_points = data.get("key_points", data.get("deliverables", []))
953
+ if key_points:
954
+ header = "#### ํ•ต์‹ฌ ํฌ์ธํŠธ" if language == "KR" else "#### Key Points"
955
+ lines.append(header)
956
+ for point in key_points[:5]:
957
+ lines.append(f"- {point}")
958
+ lines.append("")
959
+
960
+ return "\n".join(lines)
961
+
962
+ # ===== ์œ ํ‹ธ๋ฆฌํ‹ฐ =====
963
+
964
+ @classmethod
965
+ def extract_typed_data(cls, element_outputs: Dict, question_type: str) -> Dict:
966
+ """
967
+ Element ์ถœ๋ ฅ์—์„œ ์งˆ๋ฌธ ์œ ํ˜•์— ๋งž๋Š” ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ ์ถ”์ถœ
968
+
969
+ ์‹ค์ œ LLM ์‘๋‹ต์—์„œ ํŒจํ„ด์„ ์ธ์‹ํ•˜์—ฌ ๊ตฌ์กฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
970
+ """
971
+ typed_data = {}
972
+
973
+ fire_data = element_outputs.get("็ซ", {})
974
+ water_data = element_outputs.get("ๆฐด", {})
975
+ wood_data = element_outputs.get("ๆœจ", {})
976
+ metal_data = element_outputs.get("้‡‘", {})
977
+
978
+ # ๊ณตํ†ต ๋ฐ์ดํ„ฐ
979
+ typed_data["main_content"] = fire_data.get("result", "")
980
+ typed_data["key_points"] = fire_data.get("deliverables", [])
981
+ typed_data["findings"] = water_data.get("findings", [])
982
+ typed_data["risks"] = metal_data.get("risks", [])
983
+
984
+ # ์˜ˆ์ธก ์œ ํ˜• ํŠนํ™”
985
+ if question_type == "prediction":
986
+ # ์‹œ๋‚˜๋ฆฌ์˜ค ์ถ”์ถœ ์‹œ๋„
987
+ result_text = str(fire_data.get("result", ""))
988
+ scenarios = cls._extract_scenarios(result_text)
989
+ if scenarios:
990
+ typed_data["scenarios"] = scenarios
991
+
992
+ # ๋ณ€์ˆ˜ ์ถ”์ถœ
993
+ variables = []
994
+ for risk in metal_data.get("risks", []):
995
+ variables.append({
996
+ "name": str(risk)[:30],
997
+ "description": str(risk),
998
+ "impact": "high" if any(kw in str(risk).lower() for kw in ["์ค‘์š”", "ํ•ต์‹ฌ", "critical", "major"]) else "medium"
999
+ })
1000
+ typed_data["key_variables"] = variables
1001
+
1002
+ # ๋น„๊ต ์œ ํ˜• ํŠนํ™”
1003
+ elif question_type == "comparison":
1004
+ # ๋น„๊ต ํ•ญ๋ชฉ ์ถ”์ถœ ์‹œ๋„
1005
+ typed_data["comparison_items"] = []
1006
+ typed_data["criteria"] = []
1007
+ typed_data["recommendation"] = wood_data.get("key_insight", "")
1008
+
1009
+ # ์ „๋žต ์œ ํ˜• ํŠนํ™”
1010
+ elif question_type == "strategy":
1011
+ # ๋ชฉํ‘œ ์ถ”์ถœ
1012
+ objectives = []
1013
+ for idea in wood_data.get("ideas", []):
1014
+ objectives.append({
1015
+ "name": str(idea)[:50],
1016
+ "description": str(idea)
1017
+ })
1018
+ typed_data["objectives"] = objectives
1019
+ typed_data["kpis"] = []
1020
+
1021
+ return typed_data
1022
+
1023
+ @classmethod
1024
+ def _extract_scenarios(cls, text: str) -> List[Dict]:
1025
+ """ํ…์ŠคํŠธ์—์„œ ์‹œ๋‚˜๋ฆฌ์˜ค ํŒจํ„ด ์ถ”์ถœ"""
1026
+ scenarios = []
1027
+
1028
+ # ์‹œ๋‚˜๋ฆฌ์˜ค ํŒจํ„ด ๋งค์นญ
1029
+ patterns = [
1030
+ r'์‹œ๋‚˜๋ฆฌ์˜ค\s*[A-Za-z0-9๊ฐ€-ํžฃ]+[:\s]+(.{10,100})',
1031
+ r'Scenario\s*[A-Za-z0-9]+[:\s]+(.{10,100})',
1032
+ r'(\d+)\s*[%]?\s*ํ™•๋ฅ [๋กœ์œผ]?\s+(.{10,80})',
1033
+ r'(\d+)\s*[%]\s*probability[:\s]+(.{10,80})',
1034
+ ]
1035
+
1036
+ for pattern in patterns:
1037
+ matches = re.findall(pattern, text, re.IGNORECASE)
1038
+ for match in matches[:4]:
1039
+ if isinstance(match, tuple):
1040
+ scenarios.append({
1041
+ "name": f"Scenario",
1042
+ "probability": match[0] if match[0].isdigit() else "?",
1043
+ "description": match[-1] if len(match) > 1 else match[0],
1044
+ "drivers": "-",
1045
+ "timeline": "-"
1046
+ })
1047
+ else:
1048
+ scenarios.append({
1049
+ "name": "Scenario",
1050
+ "description": match,
1051
+ "probability": "?",
1052
+ "drivers": "-",
1053
+ "timeline": "-"
1054
+ })
1055
+
1056
+ return scenarios[:4]
1057
+
1058
+
1059
+ # ==================== ProfessionalReportGenerator (P0-3) ====================
1060
+
1061
+ class ProfessionalReportGenerator:
1062
+ """
1063
+ ์ „๋ฌธ ๋ณด๊ณ ์„œ ์ƒ์„ฑ๊ธฐ (ReportGenerator ๋Œ€์ฒด)
1064
+
1065
+ ๊ธฐ์กด ReportGenerator๋ฅผ ํ™•์žฅํ•˜์—ฌ:
1066
+ - Executive Summary ์ถ”๊ฐ€
1067
+ - ์งˆ๋ฌธ ์œ ํ˜•๋ณ„ ๋งž์ถค ํฌ๋งท
1068
+ - ์ถœ์ฒ˜ ๊ด€๋ฆฌ ํ†ตํ•ฉ
1069
+ - ํ’ˆ์งˆ ๋Œ€์‹œ๋ณด๋“œ ์ƒ๋‹จ ๋ฐฐ์น˜
1070
+ """
1071
+
1072
+ def __init__(self, source_manager: Optional[SourceManager] = None):
1073
+ self.source_manager = source_manager or SourceManager()
1074
+
1075
+ def generate(self, state, stats: Dict, source_manager: Optional[SourceManager] = None) -> str:
1076
+ """
1077
+ ์ „๋ฌธ ๋ณด๊ณ ์„œ ์ƒ์„ฑ
1078
+
1079
+ Args:
1080
+ state: CycleState ๊ฐ์ฒด
1081
+ stats: ๋ฉ”๋ชจ๋ฆฌ ํ†ต๊ณ„
1082
+ source_manager: ์ถœ์ฒ˜ ๊ด€๋ฆฌ์ž (์„ ํƒ)
1083
+
1084
+ Returns:
1085
+ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์˜ ์ „๋ฌธ ๋ณด๊ณ ์„œ
1086
+ """
1087
+ if source_manager:
1088
+ self.source_manager = source_manager
1089
+
1090
+ language = getattr(state, 'language', 'EN')
1091
+
1092
+ # ๋ฐ์ดํ„ฐ ์ถ”์ถœ
1093
+ goal = state.goal
1094
+ score = state.satisfaction_score
1095
+ iterations = state.iteration
1096
+ element_outputs = state.element_outputs
1097
+ question_type = getattr(state, 'goal_clarity', None)
1098
+ if question_type and hasattr(question_type, 'goal_type'):
1099
+ question_type = question_type.goal_type
1100
+ else:
1101
+ question_type = self._detect_question_type(goal)
1102
+
1103
+ # Element ๋ฐ์ดํ„ฐ
1104
+ earth_data = element_outputs.get("ๅœŸ", {})
1105
+ metal_data = element_outputs.get("้‡‘", {})
1106
+ water_data = element_outputs.get("ๆฐด", {})
1107
+ wood_data = element_outputs.get("ๆœจ", {})
1108
+ fire_data = element_outputs.get("็ซ", {})
1109
+
1110
+ # ๋ฉ”ํƒ€์ธ์ง€ ๋ฐ์ดํ„ฐ
1111
+ metacog_assessments = getattr(state, 'metacog_assessments', [])
1112
+
1113
+ # ๋ณด๊ณ ์„œ ์ƒ์„ฑ
1114
+ if language == "KR":
1115
+ return self._generate_korean(
1116
+ goal, score, iterations, question_type,
1117
+ earth_data, metal_data, water_data, wood_data, fire_data,
1118
+ metacog_assessments, state.session_id
1119
+ )
1120
+ else:
1121
+ return self._generate_english(
1122
+ goal, score, iterations, question_type,
1123
+ earth_data, metal_data, water_data, wood_data, fire_data,
1124
+ metacog_assessments, state.session_id
1125
+ )
1126
+
1127
+ def _generate_english(self, goal, score, iterations, question_type,
1128
+ earth_data, metal_data, water_data, wood_data, fire_data,
1129
+ metacog_assessments, session_id) -> str:
1130
+ """์˜์–ด ๋ณด๊ณ ์„œ ์ƒ์„ฑ"""
1131
+ sections = []
1132
+
1133
+ # ===== 1. HEADER =====
1134
+ sections.append(self._header_section_en(goal, question_type))
1135
+
1136
+ # ===== 2. EXECUTIVE SUMMARY =====
1137
+ sections.append(self._executive_summary_en(
1138
+ goal, score, iterations, fire_data, wood_data, metacog_assessments
1139
+ ))
1140
+
1141
+ # ===== 3. QUALITY DASHBOARD =====
1142
+ sections.append(self._quality_dashboard_en(metacog_assessments, score))
1143
+
1144
+ # ===== 4. MAIN ANALYSIS RESULT =====
1145
+ sections.append(self._main_result_en(fire_data, earth_data))
1146
+
1147
+ # ===== 5. TYPE-SPECIFIC FORMATTED DATA =====
1148
+ typed_data = TypedOutputFormatter.extract_typed_data(
1149
+ {"ๅœŸ": earth_data, "้‡‘": metal_data, "ๆฐด": water_data, "ๆœจ": wood_data, "็ซ": fire_data},
1150
+ question_type
1151
+ )
1152
+ type_section = TypedOutputFormatter.format(question_type, typed_data, "EN")
1153
+ if type_section.strip():
1154
+ sections.append(type_section)
1155
+
1156
+ # ===== 6. KEY INSIGHTS =====
1157
+ sections.append(self._insights_section_en(wood_data))
1158
+
1159
+ # ===== 7. VERIFIED FACTS & RISKS =====
1160
+ sections.append(self._facts_risks_section_en(metal_data))
1161
+
1162
+ # ===== 8. RESEARCH FINDINGS =====
1163
+ sections.append(self._findings_section_en(water_data))
1164
+
1165
+ # ===== 9. SOURCES & REFERENCES =====
1166
+ sections.append(self.source_manager.format_references_section("EN"))
1167
+
1168
+ # ===== 10. ANALYSIS METADATA =====
1169
+ sections.append(self._metadata_section_en(session_id, iterations, score))
1170
+
1171
+ return "\n".join(filter(None, sections))
1172
+
1173
+ def _generate_korean(self, goal, score, iterations, question_type,
1174
+ earth_data, metal_data, water_data, wood_data, fire_data,
1175
+ metacog_assessments, session_id) -> str:
1176
+ """ํ•œ๊ตญ์–ด ๋ณด๊ณ ์„œ ์ƒ์„ฑ"""
1177
+ sections = []
1178
+
1179
+ # ===== 1. HEADER =====
1180
+ sections.append(self._header_section_kr(goal, question_type))
1181
+
1182
+ # ===== 2. EXECUTIVE SUMMARY =====
1183
+ sections.append(self._executive_summary_kr(
1184
+ goal, score, iterations, fire_data, wood_data, metacog_assessments
1185
+ ))
1186
+
1187
+ # ===== 3. QUALITY DASHBOARD =====
1188
+ sections.append(self._quality_dashboard_kr(metacog_assessments, score))
1189
+
1190
+ # ===== 4. MAIN ANALYSIS RESULT =====
1191
+ sections.append(self._main_result_kr(fire_data, earth_data))
1192
+
1193
+ # ===== 5. TYPE-SPECIFIC FORMATTED DATA =====
1194
+ typed_data = TypedOutputFormatter.extract_typed_data(
1195
+ {"ๅœŸ": earth_data, "้‡‘": metal_data, "ๆฐด": water_data, "ๆœจ": wood_data, "็ซ": fire_data},
1196
+ question_type
1197
+ )
1198
+ type_section = TypedOutputFormatter.format(question_type, typed_data, "KR")
1199
+ if type_section.strip():
1200
+ sections.append(type_section)
1201
+
1202
+ # ===== 6. KEY INSIGHTS =====
1203
+ sections.append(self._insights_section_kr(wood_data))
1204
+
1205
+ # ===== 7. VERIFIED FACTS & RISKS =====
1206
+ sections.append(self._facts_risks_section_kr(metal_data))
1207
+
1208
+ # ===== 8. RESEARCH FINDINGS =====
1209
+ sections.append(self._findings_section_kr(water_data))
1210
+
1211
+ # ===== 9. SOURCES & REFERENCES =====
1212
+ sections.append(self.source_manager.format_references_section("KR"))
1213
+
1214
+ # ===== 10. ANALYSIS METADATA =====
1215
+ sections.append(self._metadata_section_kr(session_id, iterations, score))
1216
+
1217
+ return "\n".join(filter(None, sections))
1218
+
1219
+ # ===== Section Generators (English) =====
1220
+
1221
+ def _header_section_en(self, goal: str, question_type: str) -> str:
1222
+ type_header = TypedOutputFormatter.get_type_header(question_type, "EN")
1223
+ return f"""# ๐ŸŽฏ Analysis Report
1224
+
1225
+ ---
1226
+
1227
+ ## ๐Ÿ“‹ Question
1228
+ > **{goal}**
1229
+
1230
+ {type_header}
1231
+
1232
+ ---
1233
+ """
1234
+
1235
+ def _executive_summary_en(self, goal, score, iterations, fire_data, wood_data, metacog_assessments) -> str:
1236
+ """Executive Summary ์„น์…˜"""
1237
+ lines = ["## ๐Ÿ“Š Executive Summary", ""]
1238
+
1239
+ # ํ•ต์‹ฌ ๊ฒฐ๋ก  ์ถ”์ถœ (3์ค„)
1240
+ main_result = fire_data.get("result", "")
1241
+ key_insight = wood_data.get("key_insight", wood_data.get("reframe", ""))
1242
+ deliverables = fire_data.get("deliverables", [])
1243
+
1244
+ conclusions = []
1245
+ if main_result:
1246
+ # ์ฒซ ๋ฌธ์žฅ ๋˜๋Š” ํ•ต์‹ฌ ๋ถ€๋ถ„ ์ถ”์ถœ
1247
+ first_sentence = main_result.split('.')[0][:150] if main_result else ""
1248
+ if first_sentence:
1249
+ conclusions.append(first_sentence)
1250
+ if key_insight:
1251
+ conclusions.append(str(key_insight)[:100])
1252
+ if deliverables and len(conclusions) < 3:
1253
+ for d in deliverables[:2]:
1254
+ if len(conclusions) >= 3:
1255
+ break
1256
+ conclusions.append(str(d)[:80])
1257
+
1258
+ if conclusions:
1259
+ lines.append("### ๐ŸŽฏ Key Conclusions")
1260
+ for i, c in enumerate(conclusions[:3], 1):
1261
+ lines.append(f"{i}. {c}{'...' if len(c) >= 80 else ''}")
1262
+ lines.append("")
1263
+
1264
+ # ์‹ ๋ขฐ๋„ ๋ฐ”
1265
+ score_bar = 'โ–ˆ' * int(score * 10) + 'โ–‘' * (10 - int(score * 10))
1266
+ avg_quality = 0
1267
+ if metacog_assessments:
1268
+ avg_quality = sum(a.overall_score for a in metacog_assessments) / len(metacog_assessments)
1269
+ quality_bar = 'โ–ˆ' * int(avg_quality * 10) + 'โ–‘' * (10 - int(avg_quality * 10))
1270
+
1271
+ lines.append("### ๐Ÿ“ˆ Analysis Metrics")
1272
+ lines.append(f"| Metric | Score |")
1273
+ lines.append(f"|--------|-------|")
1274
+ lines.append(f"| Confidence | {score_bar} **{score:.0%}** |")
1275
+ lines.append(f"| Quality | {quality_bar} **{avg_quality:.0%}** |")
1276
+ lines.append(f"| Iterations | **{iterations}** cycles |")
1277
+ lines.append("")
1278
+
1279
+ return "\n".join(lines)
1280
+
1281
+ def _quality_dashboard_en(self, metacog_assessments, score) -> str:
1282
+ """ํ’ˆ์งˆ ๋Œ€์‹œ๋ณด๋“œ ์„น์…˜"""
1283
+ if not metacog_assessments:
1284
+ return ""
1285
+
1286
+ lines = ["## ๐Ÿง  Quality Dashboard", ""]
1287
+
1288
+ # ํ‰๊ท  ๊ณ„์‚ฐ
1289
+ avg_factual = sum(a.factual_confidence for a in metacog_assessments) / len(metacog_assessments)
1290
+ avg_logic = sum(a.logical_coherence for a in metacog_assessments) / len(metacog_assessments)
1291
+ avg_complete = sum(a.completeness for a in metacog_assessments) / len(metacog_assessments)
1292
+ avg_specific = sum(a.specificity for a in metacog_assessments) / len(metacog_assessments)
1293
+
1294
+ lines.append("| Dimension | Score | Status |")
1295
+ lines.append("|-----------|-------|--------|")
1296
+
1297
+ for name, val in [
1298
+ ("Factual Grounding", avg_factual),
1299
+ ("Logical Coherence", avg_logic),
1300
+ ("Completeness", avg_complete),
1301
+ ("Specificity", avg_specific)
1302
+ ]:
1303
+ bar = 'โ–ˆ' * int(val * 10) + 'โ–‘' * (10 - int(val * 10))
1304
+ status = "โœ…" if val >= 0.7 else "โš ๏ธ" if val >= 0.5 else "โŒ"
1305
+ lines.append(f"| {name} | {bar} {val:.0%} | {status} |")
1306
+
1307
+ lines.append("")
1308
+
1309
+ # ๊ฐœ์„  ๊ถŒ์žฅ์‚ฌํ•ญ
1310
+ all_recommendations = []
1311
+ for a in metacog_assessments[-3:]: # ์ตœ๊ทผ 3๊ฐœ
1312
+ all_recommendations.extend(a.recommendations)
1313
+
1314
+ if all_recommendations:
1315
+ unique_recs = list(dict.fromkeys(all_recommendations))[:3]
1316
+ lines.append("### ๐Ÿ’ก Improvement Suggestions")
1317
+ for rec in unique_recs:
1318
+ lines.append(f"- {rec}")
1319
+ lines.append("")
1320
+
1321
+ return "\n".join(lines)
1322
+
1323
+ def _main_result_en(self, fire_data, earth_data) -> str:
1324
+ """๋ฉ”์ธ ๊ฒฐ๊ณผ ์„น์…˜"""
1325
+ lines = ["## ๐Ÿ’ก Main Analysis Result", ""]
1326
+
1327
+ main_result = fire_data.get("result", "")
1328
+ if main_result:
1329
+ lines.append(main_result)
1330
+ lines.append("")
1331
+ else:
1332
+ assessment = earth_data.get("assessment", "")
1333
+ if assessment:
1334
+ lines.append(assessment)
1335
+ lines.append("")
1336
+ else:
1337
+ lines.append("*Analysis not completed. Please increase iteration count and try again.*")
1338
+ lines.append("")
1339
+
1340
+ return "\n".join(lines)
1341
+
1342
+ def _insights_section_en(self, wood_data) -> str:
1343
+ """์ธ์‚ฌ์ดํŠธ ์„น์…˜"""
1344
+ lines = []
1345
+
1346
+ key_insight = wood_data.get("key_insight", wood_data.get("reframe", ""))
1347
+ top3 = wood_data.get("selected_top3", wood_data.get("ideas", []))
1348
+
1349
+ if key_insight or top3:
1350
+ lines.append("## ๐Ÿ”‘ Key Insights")
1351
+ lines.append("")
1352
+
1353
+ if key_insight:
1354
+ lines.append(f"> ๐Ÿ’ก **Core Insight**: {key_insight}")
1355
+ lines.append("")
1356
+
1357
+ if top3:
1358
+ lines.append("### Top Ideas")
1359
+ for i, idea in enumerate(top3[:3], 1):
1360
+ lines.append(f"**{i}.** {idea}")
1361
+ lines.append("")
1362
+
1363
+ return "\n".join(lines)
1364
+
1365
+ def _facts_risks_section_en(self, metal_data) -> str:
1366
+ """๊ฒ€์ฆ๋œ ์‚ฌ์‹ค ๋ฐ ๋ฆฌ์Šคํฌ ์„น์…˜"""
1367
+ lines = []
1368
+
1369
+ verified = metal_data.get("verified_facts", [])
1370
+ risks = metal_data.get("risks", [])
1371
+ critiques = metal_data.get("critiques", [])
1372
+
1373
+ if verified or risks or critiques:
1374
+ lines.append("## โš–๏ธ Verification & Risks")
1375
+ lines.append("")
1376
+
1377
+ if verified:
1378
+ lines.append("### โœ… Verified Facts")
1379
+ for fact in verified[:5]:
1380
+ lines.append(f"- {fact}")
1381
+ lines.append("")
1382
+
1383
+ if risks:
1384
+ lines.append("### โš ๏ธ Identified Risks")
1385
+ for risk in risks[:4]:
1386
+ lines.append(f"- ๐Ÿ”ธ {risk}")
1387
+ lines.append("")
1388
+
1389
+ if critiques:
1390
+ lines.append("### ๐Ÿ” Critical Points")
1391
+ for critique in critiques[:3]:
1392
+ lines.append(f"- {critique}")
1393
+ lines.append("")
1394
+
1395
+ return "\n".join(lines)
1396
+
1397
+ def _findings_section_en(self, water_data) -> str:
1398
+ """์—ฐ๊ตฌ ๋ฐœ๊ฒฌ ์„น์…˜"""
1399
+ lines = []
1400
+
1401
+ findings = water_data.get("findings", [])
1402
+ key_data = water_data.get("key_data", [])
1403
+ expert_views = water_data.get("expert_views", [])
1404
+
1405
+ if findings or key_data:
1406
+ lines.append("## ๐Ÿ“Š Research Findings")
1407
+ lines.append("")
1408
+
1409
+ if findings:
1410
+ lines.append("### Key Findings")
1411
+ for finding in findings[:5]:
1412
+ lines.append(f"- {finding}")
1413
+ lines.append("")
1414
+
1415
+ if key_data:
1416
+ lines.append("### Supporting Data")
1417
+ for data in key_data[:3]:
1418
+ lines.append(f"- ๐Ÿ“ˆ {data}")
1419
+ lines.append("")
1420
+
1421
+ if expert_views:
1422
+ lines.append("### Expert Perspectives")
1423
+ for view in expert_views[:2]:
1424
+ lines.append(f"- ๐Ÿ’ฌ {view}")
1425
+ lines.append("")
1426
+
1427
+ return "\n".join(lines)
1428
+
1429
+ def _metadata_section_en(self, session_id, iterations, score) -> str:
1430
+ """๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„น์…˜"""
1431
+ from datetime import datetime
1432
+
1433
+ return f"""---
1434
+
1435
+ ## ๐Ÿ“‹ Analysis Information
1436
+
1437
+ | Item | Value |
1438
+ |------|-------|
1439
+ | Session ID | `{session_id}` |
1440
+ | Iterations | {iterations} cycles |
1441
+ | Final Confidence | {score:.0%} |
1442
+ | Generated At | {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} |
1443
+ | Engine | AETHER Proto-AGI v2.2 |
1444
+
1445
+ ---
1446
+
1447
+ *This report was generated by AETHER Proto-AGI (SOMA Five Elements ยท SLAI Self-Learning ยท MAIA Emergence)*
1448
+ """
1449
+
1450
+ # ===== Section Generators (Korean) =====
1451
+
1452
+ def _header_section_kr(self, goal: str, question_type: str) -> str:
1453
+ type_header = TypedOutputFormatter.get_type_header(question_type, "KR")
1454
+ return f"""# ๐ŸŽฏ ๋ถ„์„ ๋ณด๊ณ ์„œ
1455
+
1456
+ ---
1457
+
1458
+ ## ๐Ÿ“‹ ์งˆ๋ฌธ
1459
+ > **{goal}**
1460
+
1461
+ {type_header}
1462
+
1463
+ ---
1464
+ """
1465
+
1466
+ def _executive_summary_kr(self, goal, score, iterations, fire_data, wood_data, metacog_assessments) -> str:
1467
+ """Executive Summary ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1468
+ lines = ["## ๐Ÿ“Š ํ•ต์‹ฌ ์š”์•ฝ (Executive Summary)", ""]
1469
+
1470
+ # ํ•ต์‹ฌ ๊ฒฐ๋ก  ์ถ”์ถœ (3์ค„)
1471
+ main_result = fire_data.get("result", "")
1472
+ key_insight = wood_data.get("key_insight", wood_data.get("reframe", ""))
1473
+ deliverables = fire_data.get("deliverables", [])
1474
+
1475
+ conclusions = []
1476
+ if main_result:
1477
+ first_sentence = main_result.split('.')[0][:150] if main_result else ""
1478
+ if first_sentence:
1479
+ conclusions.append(first_sentence)
1480
+ if key_insight:
1481
+ conclusions.append(str(key_insight)[:100])
1482
+ if deliverables and len(conclusions) < 3:
1483
+ for d in deliverables[:2]:
1484
+ if len(conclusions) >= 3:
1485
+ break
1486
+ conclusions.append(str(d)[:80])
1487
+
1488
+ if conclusions:
1489
+ lines.append("### ๐ŸŽฏ ํ•ต์‹ฌ ๊ฒฐ๋ก ")
1490
+ for i, c in enumerate(conclusions[:3], 1):
1491
+ lines.append(f"{i}. {c}{'...' if len(c) >= 80 else ''}")
1492
+ lines.append("")
1493
+
1494
+ # ์‹ ๋ขฐ๋„ ๋ฐ”
1495
+ score_bar = 'โ–ˆ' * int(score * 10) + 'โ–‘' * (10 - int(score * 10))
1496
+ avg_quality = 0
1497
+ if metacog_assessments:
1498
+ avg_quality = sum(a.overall_score for a in metacog_assessments) / len(metacog_assessments)
1499
+ quality_bar = 'โ–ˆ' * int(avg_quality * 10) + 'โ–‘' * (10 - int(avg_quality * 10))
1500
+
1501
+ lines.append("### ๐Ÿ“ˆ ๋ถ„์„ ์ง€ํ‘œ")
1502
+ lines.append(f"| ์ง€ํ‘œ | ์ ์ˆ˜ |")
1503
+ lines.append(f"|------|------|")
1504
+ lines.append(f"| ์‹ ๋ขฐ๋„ | {score_bar} **{score:.0%}** |")
1505
+ lines.append(f"| ํ’ˆ์งˆ | {quality_bar} **{avg_quality:.0%}** |")
1506
+ lines.append(f"| ์ˆœํ™˜ | **{iterations}**ํšŒ |")
1507
+ lines.append("")
1508
+
1509
+ return "\n".join(lines)
1510
+
1511
+ def _quality_dashboard_kr(self, metacog_assessments, score) -> str:
1512
+ """ํ’ˆ์งˆ ๋Œ€์‹œ๋ณด๋“œ ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1513
+ if not metacog_assessments:
1514
+ return ""
1515
+
1516
+ lines = ["## ๐Ÿง  ํ’ˆ์งˆ ๋Œ€์‹œ๋ณด๋“œ", ""]
1517
+
1518
+ avg_factual = sum(a.factual_confidence for a in metacog_assessments) / len(metacog_assessments)
1519
+ avg_logic = sum(a.logical_coherence for a in metacog_assessments) / len(metacog_assessments)
1520
+ avg_complete = sum(a.completeness for a in metacog_assessments) / len(metacog_assessments)
1521
+ avg_specific = sum(a.specificity for a in metacog_assessments) / len(metacog_assessments)
1522
+
1523
+ lines.append("| ์ฐจ์› | ์ ์ˆ˜ | ์ƒํƒœ |")
1524
+ lines.append("|------|------|------|")
1525
+
1526
+ for name, val in [
1527
+ ("์‚ฌ์‹ค ๊ทผ๊ฑฐ", avg_factual),
1528
+ ("๋…ผ๋ฆฌ์  ์ผ๊ด€์„ฑ", avg_logic),
1529
+ ("์™„์„ฑ๋„", avg_complete),
1530
+ ("๊ตฌ์ฒด์„ฑ", avg_specific)
1531
+ ]:
1532
+ bar = 'โ–ˆ' * int(val * 10) + 'โ–‘' * (10 - int(val * 10))
1533
+ status = "โœ…" if val >= 0.7 else "โš ๏ธ" if val >= 0.5 else "โŒ"
1534
+ lines.append(f"| {name} | {bar} {val:.0%} | {status} |")
1535
+
1536
+ lines.append("")
1537
+
1538
+ # ๊ฐœ์„  ๊ถŒ์žฅ์‚ฌํ•ญ
1539
+ all_recommendations = []
1540
+ for a in metacog_assessments[-3:]:
1541
+ all_recommendations.extend(a.recommendations)
1542
+
1543
+ if all_recommendations:
1544
+ unique_recs = list(dict.fromkeys(all_recommendations))[:3]
1545
+ lines.append("### ๐Ÿ’ก ๊ฐœ์„  ๊ถŒ์žฅ์‚ฌํ•ญ")
1546
+ for rec in unique_recs:
1547
+ lines.append(f"- {rec}")
1548
+ lines.append("")
1549
+
1550
+ return "\n".join(lines)
1551
+
1552
+ def _main_result_kr(self, fire_data, earth_data) -> str:
1553
+ """๋ฉ”์ธ ๊ฒฐ๊ณผ ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1554
+ lines = ["## ๐Ÿ’ก ํ•ต์‹ฌ ๋ถ„์„ ๊ฒฐ๊ณผ", ""]
1555
+
1556
+ main_result = fire_data.get("result", "")
1557
+ if main_result:
1558
+ lines.append(main_result)
1559
+ lines.append("")
1560
+ else:
1561
+ assessment = earth_data.get("assessment", "")
1562
+ if assessment:
1563
+ lines.append(assessment)
1564
+ lines.append("")
1565
+ else:
1566
+ lines.append("*๋ถ„์„์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ˆœํ™˜ ํšŸ์ˆ˜๋ฅผ ๏ฟฝ๏ฟฝ๋ ค ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.*")
1567
+ lines.append("")
1568
+
1569
+ return "\n".join(lines)
1570
+
1571
+ def _insights_section_kr(self, wood_data) -> str:
1572
+ """์ธ์‚ฌ์ดํŠธ ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1573
+ lines = []
1574
+
1575
+ key_insight = wood_data.get("key_insight", wood_data.get("reframe", ""))
1576
+ top3 = wood_data.get("selected_top3", wood_data.get("ideas", []))
1577
+
1578
+ if key_insight or top3:
1579
+ lines.append("## ๐Ÿ”‘ ํ•ต์‹ฌ ์ธ์‚ฌ์ดํŠธ")
1580
+ lines.append("")
1581
+
1582
+ if key_insight:
1583
+ lines.append(f"> ๐Ÿ’ก **ํ•ต์‹ฌ ํ†ต์ฐฐ**: {key_insight}")
1584
+ lines.append("")
1585
+
1586
+ if top3:
1587
+ lines.append("### ์ฃผ์š” ์•„์ด๋””์–ด")
1588
+ for i, idea in enumerate(top3[:3], 1):
1589
+ lines.append(f"**{i}.** {idea}")
1590
+ lines.append("")
1591
+
1592
+ return "\n".join(lines)
1593
+
1594
+ def _facts_risks_section_kr(self, metal_data) -> str:
1595
+ """๊ฒ€์ฆ๋œ ์‚ฌ์‹ค ๋ฐ ๋ฆฌ์Šคํฌ ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1596
+ lines = []
1597
+
1598
+ verified = metal_data.get("verified_facts", [])
1599
+ risks = metal_data.get("risks", [])
1600
+ critiques = metal_data.get("critiques", [])
1601
+
1602
+ if verified or risks or critiques:
1603
+ lines.append("## โš–๏ธ ๊ฒ€์ฆ ๋ฐ ๋ฆฌ์Šคํฌ")
1604
+ lines.append("")
1605
+
1606
+ if verified:
1607
+ lines.append("### โœ… ๊ฒ€์ฆ๋œ ์‚ฌ์‹ค")
1608
+ for fact in verified[:5]:
1609
+ lines.append(f"- {fact}")
1610
+ lines.append("")
1611
+
1612
+ if risks:
1613
+ lines.append("### โš ๏ธ ์‹๋ณ„๋œ ๋ฆฌ์Šคํฌ")
1614
+ for risk in risks[:4]:
1615
+ lines.append(f"- ๐Ÿ”ธ {risk}")
1616
+ lines.append("")
1617
+
1618
+ if critiques:
1619
+ lines.append("### ๐Ÿ” ๋น„ํŒ์  ๊ฒ€ํ† ")
1620
+ for critique in critiques[:3]:
1621
+ lines.append(f"- {critique}")
1622
+ lines.append("")
1623
+
1624
+ return "\n".join(lines)
1625
+
1626
+ def _findings_section_kr(self, water_data) -> str:
1627
+ """์—ฐ๊ตฌ ๋ฐœ๊ฒฌ ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1628
+ lines = []
1629
+
1630
+ findings = water_data.get("findings", [])
1631
+ key_data = water_data.get("key_data", [])
1632
+ expert_views = water_data.get("expert_views", [])
1633
+
1634
+ if findings or key_data:
1635
+ lines.append("## ๐Ÿ“Š ์—ฐ๊ตฌ ๋ฐœ๊ฒฌ")
1636
+ lines.append("")
1637
+
1638
+ if findings:
1639
+ lines.append("### ์ฃผ์š” ๋ฐœ๊ฒฌ")
1640
+ for finding in findings[:5]:
1641
+ lines.append(f"- {finding}")
1642
+ lines.append("")
1643
+
1644
+ if key_data:
1645
+ lines.append("### ๊ทผ๊ฑฐ ๋ฐ์ดํ„ฐ")
1646
+ for data in key_data[:3]:
1647
+ lines.append(f"- ๐Ÿ“ˆ {data}")
1648
+ lines.append("")
1649
+
1650
+ if expert_views:
1651
+ lines.append("### ์ „๋ฌธ๊ฐ€ ์˜๊ฒฌ")
1652
+ for view in expert_views[:2]:
1653
+ lines.append(f"- ๐Ÿ’ฌ {view}")
1654
+ lines.append("")
1655
+
1656
+ return "\n".join(lines)
1657
+
1658
+ def _metadata_section_kr(self, session_id, iterations, score) -> str:
1659
+ """๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„น์…˜ (ํ•œ๊ตญ์–ด)"""
1660
+ from datetime import datetime
1661
+
1662
+ return f"""---
1663
+
1664
+ ## ๐Ÿ“‹ ๋ถ„์„ ์ •๋ณด
1665
+
1666
+ | ํ•ญ๋ชฉ | ๊ฐ’ |
1667
+ |------|-----|
1668
+ | ์„ธ์…˜ ID | `{session_id}` |
1669
+ | ๋ถ„์„ ์ˆœํ™˜ | {iterations}ํšŒ |
1670
+ | ์ตœ์ข… ์‹ ๋ขฐ๋„ | {score:.0%} |
1671
+ | ์ƒ์„ฑ ์‹œ๊ฐ„ | {datetime.now().strftime("%Y๋…„ %m์›” %d์ผ %H:%M:%S")} |
1672
+ | ์—”์ง„ | AETHER Proto-AGI v2.2 |
1673
+
1674
+ ---
1675
+
1676
+ *์ด ๋ณด๊ณ ์„œ๋Š” AETHER Proto-AGI (SOMA ์˜คํ–‰ ์ˆœํ™˜ ยท SLAI ์ž๊ธฐํ•™์Šต ยท MAIA ์ฐฝ๋ฐœ)์— ์˜ํ•ด ์ž๋™ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.*
1677
+ """
1678
+
1679
+ # ===== Utility Methods =====
1680
+
1681
+ def _detect_question_type(self, goal: str) -> str:
1682
+ """์งˆ๋ฌธ ์œ ํ˜• ๊ฐ์ง€"""
1683
+ goal_lower = goal.lower()
1684
+
1685
+ if any(kw in goal_lower for kw in ['์˜ˆ์ธก', '์ „๋ง', '๋ ๊นŒ', '๋  ๊ฒƒ', '๋ฏธ๋ž˜', '2025', '2026', '2027', '2028', 'predict', 'forecast']):
1686
+ return "prediction"
1687
+ elif any(kw in goal_lower for kw in ['์ „๋žต', '๋ฐฉ๋ฒ•', '์–ด๋–ป๊ฒŒ', '๋ฐฉ์•ˆ', '๊ณ„ํš', '์ˆ˜๋ฆฝ', 'strategy', 'how to', 'plan']):
1688
+ return "strategy"
1689
+ elif any(kw in goal_lower for kw in ['๋น„๊ต', '์ฐจ์ด', 'vs', 'VS', '๋Œ€', '์–ด๋А ๊ฒƒ', 'compare', 'versus', 'difference']):
1690
+ return "comparison"
1691
+ elif any(kw in goal_lower for kw in ['๋ถ„์„', '์™œ', '์›์ธ', '์˜ํ–ฅ', 'ํ˜„ํ™ฉ', 'analyze', 'why', 'cause', 'impact']):
1692
+ return "analysis"
1693
+ elif any(kw in goal_lower for kw in ['ํŠนํ—ˆ', '๋ฐœ๋ช…', '์•„์ด๋””์–ด', 'ํ˜์‹ ', 'patent', 'invention', 'innovate']):
1694
+ return "invention"
1695
+ elif any(kw in goal_lower for kw in ['์†Œ์„ค', '์Šคํ† ๋ฆฌ', '์‹œ๋‚˜๋ฆฌ์˜ค', '์›นํˆฐ', 'story', 'novel', 'script']):
1696
+ return "story"
1697
+ elif any(kw in goal_lower for kw in ['์š”๋ฆฌ', '๋ ˆ์‹œํ”ผ', '์Œ์‹', 'recipe', 'cook', 'dish']):
1698
+ return "recipe"
1699
+ else:
1700
+ return "general"
1701
+
1702
+ @staticmethod
1703
+ def generate_progress(state) -> str:
1704
+ """์ง„ํ–‰ ์ค‘ ๋ณด๊ณ ์„œ (๊ธฐ์กด ํ˜ธํ™˜)"""
1705
+ progress_bar = 'โ–ˆ' * int(state.satisfaction_score * 10) + 'โ–‘' * (10 - int(state.satisfaction_score * 10))
1706
+
1707
+ current_element = ""
1708
+ if state.history:
1709
+ last = state.history[-1]
1710
+ elem_name = last.get("element", "")
1711
+ elem_map = {
1712
+ "ๅœŸ": "๐ŸŸค ๅœŸ ๊ฐ๋…", "้‡‘": "โšช ้‡‘ ๋น„ํ‰", "ๆฐด": "๐Ÿ”ต ๆฐด ๋ฆฌ์„œ์น˜",
1713
+ "ๆœจ": "๐ŸŸข ๆœจ ์ฐฝ๋ฐœ", "็ซ": "๐Ÿ”ด ็ซ ์‹คํ–‰"
1714
+ }
1715
+ current_element = elem_map.get(elem_name, elem_name)
1716
+
1717
+ language = getattr(state, 'language', 'EN')
1718
+
1719
+ if language == "KR":
1720
+ return f"""# โณ ๋ถ„์„ ์ง„ํ–‰ ์ค‘...
1721
+
1722
+ ## ๐Ÿ“‹ ์งˆ๋ฌธ
1723
+ > **{state.goal}**
1724
+
1725
+ ## ๐Ÿ“Š ํ˜„์žฌ ์ƒํƒœ
1726
+ | ํ•ญ๋ชฉ | ์ƒํƒœ |
1727
+ |------|------|
1728
+ | ์ง„ํ–‰ ์ˆœํ™˜ | {state.iteration}ํšŒ |
1729
+ | ํ˜„์žฌ ๋‹จ๊ณ„ | {current_element} |
1730
+ | ์ง„ํ–‰๋„ | {progress_bar} {state.satisfaction_score:.0%} |
1731
+
1732
+ ---
1733
+ ๐Ÿ’ก **์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋กœ๊ทธ**๋ฅผ ํŽผ์ณ์„œ ์‹ค์‹œ๊ฐ„ ๋ถ„์„ ๊ณผ์ •์„ ํ™•์ธํ•˜์„ธ์š”.
1734
+
1735
+ *ๅœŸ(๊ฐ๋…) โ†’ ้‡‘(๋น„ํ‰) โ†’ ๆฐด(๋ฆฌ์„œ์น˜) โ†’ ๆœจ(์ฐฝ๋ฐœ) โ†’ ็ซ(์‹คํ–‰) ์ˆœํ™˜ ์ค‘...*
1736
+ """
1737
+ else:
1738
+ return f"""# โณ Analysis in Progress...
1739
+
1740
+ ## ๐Ÿ“‹ Question
1741
+ > **{state.goal}**
1742
+
1743
+ ## ๐Ÿ“Š Current Status
1744
+ | Item | Status |
1745
+ |------|--------|
1746
+ | Iteration | {state.iteration} |
1747
+ | Current Stage | {current_element} |
1748
+ | Progress | {progress_bar} {state.satisfaction_score:.0%} |
1749
+
1750
+ ---
1751
+ ๐Ÿ’ก Expand **Orchestration Log** to see real-time analysis progress.
1752
+
1753
+ *Earth โ†’ Metal โ†’ Water โ†’ Wood โ†’ Fire cycle in progress...*
1754
+ """
1755
+
1756
+
1757
+ # ==================== ๊ธฐ์กด ReportGenerator์™€์˜ ํ˜ธํ™˜์„ฑ ====================
1758
+
1759
+ # ๊ธฐ์กด ์ฝ”๋“œ์—์„œ ReportGenerator๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์„ ์œ„ํ•œ ๋ž˜ํผ
1760
+ class ReportGenerator:
1761
+ """
1762
+ ๊ธฐ์กด ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ๋ž˜ํผ ํด๋ž˜์Šค
1763
+ ProfessionalReportGenerator๋กœ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.
1764
+ """
1765
+
1766
+ _instance = None
1767
+ _professional_generator = None
1768
+
1769
+ @classmethod
1770
+ def _get_generator(cls):
1771
+ if cls._professional_generator is None:
1772
+ cls._professional_generator = ProfessionalReportGenerator()
1773
+ return cls._professional_generator
1774
+
1775
+ @staticmethod
1776
+ def generate(state, stats: Dict) -> str:
1777
+ """๊ธฐ์กด ํ˜ธ์ถœ ๋ฐฉ์‹ ํ˜ธํ™˜"""
1778
+ generator = ReportGenerator._get_generator()
1779
+ return generator.generate(state, stats)
1780
+
1781
+ @staticmethod
1782
+ def generate_progress(state) -> str:
1783
+ """์ง„ํ–‰ ์ค‘ ๋ณด๊ณ ์„œ (๊ธฐ์กด ํ˜ธํ™˜)"""
1784
+ return ProfessionalReportGenerator.generate_progress(state)
1785
+
1786
+
1787
+ # ==================== ์‚ฌ์šฉ ์˜ˆ์‹œ ====================
1788
+
1789
+ if __name__ == "__main__":
1790
+ print("=" * 60)
1791
+ print("AETHER Proto-AGI v2.2 - Report Enhancement Module")
1792
+ print("=" * 60)
1793
+ print()
1794
+ print("์ด ๋ชจ๋“ˆ์˜ ํด๋ž˜์Šค๋“ค์„ core.py์— ํ†ตํ•ฉํ•˜์„ธ์š”:")
1795
+ print()
1796
+ print("1. SourceManager - ์ถœ์ฒ˜/์ธ์šฉ ๊ด€๋ฆฌ")
1797
+ print(" - add_web_source(), add_knowledge_source()")
1798
+ print(" - format_references_section()")
1799
+ print()
1800
+ print("2. TypedOutputFormatter - ์งˆ๋ฌธ ์œ ํ˜•๋ณ„ ํฌ๋งท")
1801
+ print(" - format_prediction(), format_comparison(), etc.")
1802
+ print()
1803
+ print("3. ProfessionalReportGenerator - ์ „๋ฌธ ๋ณด๊ณ ์„œ ์ƒ์„ฑ")
1804
+ print(" - Executive Summary")
1805
+ print(" - Quality Dashboard")
1806
+ print(" - Type-specific formatting")
1807
+ print(" - Source references")
1808
+ print()
1809
+ print("=" * 60)
1810
+
1811
+ # ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ
1812
+ print("\n[TypedOutputFormatter ํ…Œ์ŠคํŠธ]")
1813
+ test_data = {
1814
+ "scenarios": [
1815
+ {"name": "Scenario A", "probability": "60%", "drivers": "Strong growth", "timeline": "2025 Q2"},
1816
+ {"name": "Scenario B", "probability": "30%", "drivers": "Moderate", "timeline": "2025 Q4"},
1817
+ ],
1818
+ "key_variables": [
1819
+ {"name": "Interest Rate", "description": "Fed policy changes", "impact": "high"},
1820
+ {"name": "Tech Innovation", "description": "AI disruption", "impact": "medium"},
1821
+ ]
1822
+ }
1823
+ print(TypedOutputFormatter.format_prediction(test_data, "EN"))
1824
+
1825
+ print("\n[SourceManager ํ…Œ์ŠคํŠธ]")
1826
+ sm = SourceManager()
1827
+ sm.add_web_source("Example Article", "https://example.com/article", "This is a test", "ๆฐด")
1828
+ sm.add_web_source("Another Source", "https://test.com/page", "More content here", "้‡‘")
1829
+ print(sm.format_references_section("EN"))