Ver3.0 ๐ผ AI๋ก ํฌ์๋ฆฌํฌํธ ๋ถ์ํ๊ณ ํต์ฌ๋ง ์์ ๋ฝ์๋ด๊ธฐ

๐ผ AI๋ก ํฌ์๋ฆฌํฌํธ ๋ถ์ํ๊ณ ํต์ฌ๋ง ์์ ๋ฝ์๋ด๊ธฐ
๋ณต์กํ ํฌ์ ๋ฆฌํฌํธ, ์ด์ AI ์์ด์ ํธ๊ฐ ์์์ ์ ๋ฆฌํด๋๋ฆฝ๋๋ค! ๐
์๋
! ์ค๋์ ์ ๋ง ์ค์ฉ์ ์ด๋ฉด์๋ ์์ฒญ ํซํ ์ฃผ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ๋ฐ๋ก AI๋ฅผ ํ์ฉํด์ ํฌ์ ๋ฆฌํฌํธ๋ฅผ ์๋์ผ๋ก ๋ถ์ํ๊ณ ํต์ฌ ๋ด์ฉ๋ง ์์ ๋ฝ์๋ด๋ ๋ฐฉ๋ฒ์ด์ผ. ๐โจ
์์ฆ ํฌ์ํ๋ ์ฌ๋๋ค ์ ๋ง ๋ง์์? ์ฃผ์, ์ฝ์ธ, ๋ถ๋์ฐ... ๊ทผ๋ฐ ์ฆ๊ถ์ฌ๋ ํฌ์ ๊ธฐ๊ด์์ ๋์ค๋ ๋ฆฌํฌํธ๋ค ๋ณด๋ฉด ๋จธ๋ฆฌ ์ํ์ง ์์? ์์ญ ํ์ด์ง์ ๋ฌํ๋ PDF ํ์ผ, ๋ณต์กํ ์ฐจํธ, ์ ๋ฌธ ์ฉ์ด ๊ฐ๋ํ ๋ฌธ์ฅ๋ค... ๐ต
"์ ์ด๊ฑฐ ์ธ์ ๋ค ์ฝ์ด..." ํ๋ค๊ฐ ๊ฒฐ๊ตญ ์ ๋ชฉ๋ง ๋ณด๊ณ ๋์ด๊ฐ๋ ๊ฒฝ์ฐ๊ฐ ๋๋ถ๋ถ์ด์ง. ๊ทธ๋ฐ๋ฐ ๋ง์ด์ผ, AI ์๋ํ ๊ธฐ์ ์ ํ์ฉํ๋ฉด ์ด ๋ชจ๋ ๊ฑธ ๋ช ์ด ๋ง์ ํด๊ฒฐํ ์ ์์ด! ๐ฏ
๐ค ์ AI ํฌ์๋ฆฌํฌํธ ๋ถ์์ด ํ์ํ ๊น?
๋จผ์ ํ์ค์ ์ธ ์ด์ผ๊ธฐ๋ถํฐ ํด๋ณผ๊ฒ. ์์ฆ ํฌ์ ์์ฅ์ด ์ผ๋ง๋ ๋ณต์กํด์ก๋์ง ์์? ๐
์ผ๋ฐ ๊ฐ์ธ ํฌ์์๊ฐ ๋ง์ฃผํ๋ ํ์ค:
โข ํ๋ฃจ์๋ ์์ญ ๊ฐ์ ํฌ์ ๋ฆฌํฌํธ๊ฐ ์์์ง
โข ํ ๋ฆฌํฌํธ๋น ํ๊ท 20~50ํ์ด์ง
โข ์ ๋ฌธ ์ฉ์ด์ ๋ณต์กํ ์ฌ๋ฌด ์งํ๋ก ๊ฐ๋
โข ํต์ฌ ๋ด์ฉ์ ์ฐพ๊ธฐ ์ํด ์ ์ฒด๋ฅผ ๋ค ์ฝ์ด์ผ ํจ
โข ์๊ฐ์ ์๊ณ , ๋์น๋ฉด ๊ธฐํ๋ ๋ ์๊ฐ โฐ
์ค์ ๋ก ์ฆ๊ถ์ฌ ์ ๋๋ฆฌ์คํธ๋ค์ด ์์ฑํ ๋ฆฌํฌํธ๋ฅผ ๋ณด๋ฉด, ์ ๋ง ์ค์ํ ๋ด์ฉ์ ์ ์ฒด์ 10~20% ์ ๋์ผ. ๋๋จธ์ง๋ ๋ฐฐ๊ฒฝ ์ค๋ช
, ์์ฅ ์ํฉ, ๋ฉด์ฑ
์กฐํญ ๊ฐ์ ๊ฑฐ์ง. ๊ทผ๋ฐ ๊ทธ 10~20%๋ฅผ ์ฐพ์ผ๋ ค๋ฉด ์ ์ฒด๋ฅผ ๋ค ์ฝ์ด์ผ ํ๋ค๋ ๊ฒ ํจ์ ์ด์ผ. ๐
๐ก ์ค์ ์ฌ๋ก๋ก ๋ณด๋ ๋ฌธ์ ์
์ผ์ฑ์ ์ ํฌ์ ๋ฆฌํฌํธ ํ๋๋ฅผ ์๋ก ๋ค์ด๋ณผ๊ฒ:
โข ์ด ํ์ด์ง: 45ํ์ด์ง
โข ์ฝ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ: ์ฝ 30~40๋ถ
โข ์ค์ ํฌ์ ํ๋จ์ ํ์ํ ํต์ฌ ์ ๋ณด: 3~4ํ์ด์ง ๋ถ๋
โข ํจ์จ์ฑ: ์ฝ 10% ๋ฏธ๋ง ๐ฑ
๋ง์ฝ ํ๋ฃจ์ 5๊ฐ์ ๋ฆฌํฌํธ๋ฅผ ํ์ธํด์ผ ํ๋ค๋ฉด? 2~3์๊ฐ์ด ํ์ฉ ๋ ์๊ฐ๋ ๊ฑฐ์ง. ์ง์ฅ์ธ์ด๋ ๋ฐ์ ์ฌ๋๋ค์ ์ฌ์ค์ ๋ถ๊ฐ๋ฅํ ์ผ์ด์ผ.
๐ค AI ์์ด์ ํธ๊ฐ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก AI๊ฐ ์ด๋ป๊ฒ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋์ง ์์๋ณด์! ๐
AI ์๋ํ ์์คํ
์ ํต์ฌ ๊ธฐ๋ฅ:
1๏ธโฃ ๋ฌธ์ ์๋ ์์ง ๋ฐ ์ ์ฒ๋ฆฌ
์๋ ํฌ๋กค๋ง
์ฆ๊ถ์ฌ ์น์ฌ์ดํธ, ๊ธ์ต ํฌํธ, ์ด๋ฉ์ผ ๋ฑ์์ ์๋ก์ด ๋ฆฌํฌํธ๋ฅผ ์๋์ผ๋ก ์์งํด. ๋งค์ผ ์์นจ ์ถ๊ทผํ๋ฉด ์ด๋ฏธ ์ต์ ๋ฆฌํฌํธ๋ค์ด ์ ๋ฆฌ๋์ด ์๋ ๊ฑฐ์ง!
PDF ํ์ฑ
๋๋ถ๋ถ์ ํฌ์ ๋ฆฌํฌํธ๋ PDF ํํ์ผ. AI๋ ์ด PDF์์ ํ
์คํธ, ํ, ์ฐจํธ๋ฅผ ์๋์ผ๋ก ์ถ์ถํ๊ณ ๊ตฌ์กฐํํด. ์ฌ์ง์ด ์ค์บ๋ ์ด๋ฏธ์ง PDF๋ OCR ๊ธฐ์ ๋ก ํ
์คํธ๋ฅผ ๋ฝ์๋ผ ์ ์์ด!
๋ฐ์ดํฐ ์ ์
๋ถํ์ํ ํค๋, ํธํฐ, ๊ด๊ณ , ๋ฉด์ฑ
์กฐํญ ๊ฐ์ ๊ฑด ์๋์ผ๋ก ์ ๊ฑฐํ๊ณ , ์ค์ ๋ถ์ ๋ด์ฉ๋ง ๋จ๊ฒจ๋ฌ.
2๏ธโฃ ์์ฐ์ด ์ฒ๋ฆฌ(NLP)๋ฅผ ํตํ ๋ด์ฉ ๋ถ์
์ฌ๊ธฐ์๋ถํฐ๊ฐ ์ง์ง AI์ ๋ง๋ฒ์ด ์์๋๋ ๋ถ๋ถ์ด์ผ! โจ
ํต์ฌ ๊ธฐ์ ๋ค:
๐ฏ ๊ฐ์ฒด๋ช ์ธ์(NER)
๋ฌธ์์์ ์ค์ํ ์ ๋ณด๋ฅผ ์๋์ผ๋ก ์ฐพ์๋ด:
โข ๊ธฐ์
๋ช
: "์ผ์ฑ์ ์", "SKํ์ด๋์ค"
โข ์ฌ๋ฌด ์งํ: "์์
์ด์ต", "๋งค์ถ์ก", "PER"
โข ๋ ์ง: "2024๋
1๋ถ๊ธฐ", "2025๋
์ ๋ง"
โข ๊ธ์ก: "10์กฐ์", "15% ์ฆ๊ฐ"
์ด๋ ๊ฒ ์ถ์ถ๋ ์ ๋ณด๋ค์ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ผ์ ๋์ค์ ๊ฒ์ํ๊ฑฐ๋ ๋น๊ต ๋ถ์ํ ๋ ์์ฒญ ์ ์ฉํด! ๐
๐ฌ ๊ฐ์ฑ ๋ถ์(Sentiment Analysis)
์ ๋๋ฆฌ์คํธ๊ฐ ํด๋น ์ข
๋ชฉ์ ๋ํด ๊ธ์ ์ ์ธ์ง, ๋ถ์ ์ ์ธ์ง, ์ค๋ฆฝ์ ์ธ์ง๋ฅผ ์๋์ผ๋ก ํ๋จํด:
๊ธ์ ์ ํํ:
"์ค์ ๊ฐ์ ๊ธฐ๋", "๋ชฉํ์ฃผ๊ฐ ์ํฅ", "๋งค์ ์ถ์ฒ" โ ๐ ๊ธ์ ์ ์ +0.8
๋ถ์ ์ ํํ:
"์ค์ ๋ถ์ง ์ฐ๋ ค", "๋ชฉํ์ฃผ๊ฐ ํํฅ", "๋น์ค ์ถ์" โ ๐ ๋ถ์ ์ ์ -0.6
์ด๋ฐ ๊ฐ์ฑ ์ ์๋ฅผ ํตํด ๋ฆฌํฌํธ์ ์ ๋ฐ์ ์ธ ํค์ ํ๋์ ํ์
ํ ์ ์์ด!
๐ ํต์ฌ ๋ฌธ์ฅ ์ถ์ถ
์ ์ฒด ๋ฌธ์์์ ๊ฐ์ฅ ์ค์ํ ๋ฌธ์ฅ๋ค์ ์๋์ผ๋ก ๊ณจ๋ผ๋ด๋ ๊ธฐ์ ์ด์ผ. ์ด๋ป๊ฒ ํ๋จํ๋๊ณ ?
์ค์๋ ํ๋จ ๊ธฐ์ค:
โข ๋ฌธ์ฅ์ ์์น (์๋ก , ๊ฒฐ๋ก ๋ถ๋ถ์ ๋ฌธ์ฅ์ ์ค์๋ ๋์)
โข ํต์ฌ ํค์๋ ํฌํจ ์ฌ๋ถ
โข ์์น ์ ๋ณด ํฌํจ ์ฌ๋ถ
โข ๋ฌธ์ฅ ๊ฐ ์ฐ๊ฒฐ์ฑ
โข ์ ๋ฌธ ์ฉ์ด ๋ฐ๋
์๋ฅผ ๋ค์ด, "2024๋
1๋ถ๊ธฐ ์์
์ด์ต์ ์ ๋
๋๋น 25% ์ฆ๊ฐํ 3์กฐ์์ ๊ธฐ๋กํ ๊ฒ์ผ๋ก ์ ๋ง๋๋ค"๋ผ๋ ๋ฌธ์ฅ์ ๋์ ์ค์๋ ์ ์๋ฅผ ๋ฐ๊ฒ ๋ผ! ๐ฏ
3๏ธโฃ ๋ฅ๋ฌ๋ ๊ธฐ๋ฐ ์์ฝ ์์ฑ
์ด์ ๊ฐ์ฅ ํต์ฌ์ ์ธ ๋ถ๋ถ, ๋ฐ๋ก ์๋ ์์ฝ์ด์ผ! ๐จ
์ต๊ทผ์๋ GPT, BERT, T5 ๊ฐ์ ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ๋ค์ด ์์ฒญ๋ ์ฑ๋ฅ์ ๋ณด์ฌ์ฃผ๊ณ ์์ด. ์ด๋ฐ ๋ชจ๋ธ๋ค์ ํ์ฉํ๋ฉด:
โ
์ถ์ถ ์์ฝ(Extractive Summarization)
์๋ฌธ์์ ์ค์ํ ๋ฌธ์ฅ๋ค์ ๊ทธ๋๋ก ๋ฝ์์ ์กฐํฉํ๋ ๋ฐฉ์์ด์ผ. ์๋ฌธ์ ์ ํ์ฑ์ ์ ์งํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ง!
์์:
์๋ฌธ 45ํ์ด์ง โ ํต์ฌ ๋ฌธ์ฅ 15๊ฐ ์ถ์ถ โ 2ํ์ด์ง ์์ฝ๋ณธ ์์ฑ
โ
์์ฑ ์์ฝ(Abstractive Summarization)
AI๊ฐ ๋ด์ฉ์ ์ดํดํ๊ณ ์๋ก์ด ๋ฌธ์ฅ์ผ๋ก ์ฌ๊ตฌ์ฑํ๋ ๋ฐฉ์์ด์ผ. ๋ ์์ฐ์ค๋ฝ๊ณ ์ฝ๊ธฐ ํธํ ์์ฝ๋ฌธ์ ๋ง๋ค ์ ์์ด!
์์:
"์ผ์ฑ์ ์์ 2024๋
1๋ถ๊ธฐ ์ค์ ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ๋์ฒด ๊ฐ๊ฒฉ ์์น๊ณผ ์ค๋งํธํฐ ํ๋งค ์ฆ๊ฐ๋ก ์ ๋
๋๋น ํฌ๊ฒ ๊ฐ์ ๋ ์ ๋ง์ด๋ฉฐ, ๋ชฉํ์ฃผ๊ฐ๋ 85,000์์ผ๋ก ์ํฅ ์กฐ์ ๋์์ต๋๋ค." ๐ฑ๐พ
๐ ๏ธ ์ค์ ๊ตฌํ ๋ฐฉ๋ฒ - ๋จ๊ณ๋ณ ๊ฐ์ด๋
์, ์ด๋ก ์ ์ด ์ ๋๋ฉด ์ถฉ๋ถํ๊ณ , ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ๋ง๋๋์ง ์์๋ณด์! ์ฝ๋ฉ์ด ์ฒ์์ด๋ผ๋ ๊ฑฑ์ ๋ง. ์ฐจ๊ทผ์ฐจ๊ทผ ์ค๋ช ํด์ค๊ฒ! ๐ช
๐ง ํ์ํ ๋๊ตฌ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
๊ธฐ๋ณธ ํ๊ฒฝ:
โข Python 3.8 ์ด์
โข ๊ฐ์ํ๊ฒฝ (venv ๋๋ conda)
โข ํ
์คํธ ์๋ํฐ (VS Code ์ถ์ฒ)
ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ:
โข PyPDF2 ๋๋ pdfplumber: PDF ํ์ฑ
โข beautifulsoup4: ์น ํฌ๋กค๋ง
โข transformers: ํ๊น
ํ์ด์ค ๋ชจ๋ธ ํ์ฉ
โข openai: GPT API ์ฌ์ฉ
โข pandas: ๋ฐ์ดํฐ ์ฒ๋ฆฌ
โข nltk ๋๋ spacy: ์์ฐ์ด ์ฒ๋ฆฌ
โข selenium: ๋์ ์นํ์ด์ง ํฌ๋กค๋ง
๐ Step 1: PDF์์ ํ ์คํธ ์ถ์ถํ๊ธฐ
๊ฐ์ฅ ๋จผ์ ํด์ผ ํ ์ผ์ PDF ํ์ผ์์ ํ ์คํธ๋ฅผ ๋ฝ์๋ด๋ ๊ฑฐ์ผ. ์๊ฐ๋ณด๋ค ๊ฐ๋จํด!
import pdfplumber
import re
def extract_text_from_pdf(pdf_path):
"""PDF ํ์ผ์์ ํ
์คํธ๋ฅผ ์ถ์ถํ๋ ํจ์"""
text = ""
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
# ๊ฐ ํ์ด์ง์์ ํ
์คํธ ์ถ์ถ
page_text = page.extract_text()
if page_text:
text += page_text + "\n"
# ๋ถํ์ํ ๊ณต๋ฐฑ ์ ๊ฑฐ
text = re.sub(r'\n+', '\n', text)
text = re.sub(r' +', ' ', text)
return text
# ์ฌ์ฉ ์์
report_text = extract_text_from_pdf("์ผ์ฑ์ ์_ํฌ์๋ฆฌํฌํธ.pdf")
print(f"์ถ์ถ๋ ํ
์คํธ ๊ธธ์ด: {len(report_text)} ๊ธ์")
๐ก Pro Tip:
PDF์ ํ๋ ์ฐจํธ๊ฐ ๋ง๋ค๋ฉด pdfplumber์ extract_tables() ํจ์๋ฅผ ์ฌ์ฉํด์ ํ ๋ฐ์ดํฐ๋ ๋ฐ๋ก ์ถ์ถํ ์ ์์ด! ์ฌ๋ฌด์ ํ ๊ฐ์ ๊ฑด ํ ํํ๋ก ์ถ์ถํ๋ ๊ฒ ํจ์ฌ ์ ํํด. ๐
๐ Step 2: ํ ์คํธ ์ ์ฒ๋ฆฌ ๋ฐ ๊ตฌ์กฐํ
์ถ์ถํ ํ ์คํธ๋ฅผ AI๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ ๋ฆฌํด์ผ ํด. ์ด ๊ณผ์ ์ด ์๊ฐ๋ณด๋ค ์ค์ํด!
import re
from typing import Dict, List
def preprocess_report(text: str) -> Dict[str, any]:
"""๋ฆฌํฌํธ ํ
์คํธ๋ฅผ ๊ตฌ์กฐํํ๋ ํจ์"""
# ์น์
๊ตฌ๋ถ (์ผ๋ฐ์ ์ธ ๋ฆฌํฌํธ ๊ตฌ์กฐ)
sections = {
'title': '',
'summary': '',
'company_info': '',
'financial_analysis': '',
'investment_opinion': '',
'target_price': '',
'risks': ''
}
# ์ ๋ชฉ ์ถ์ถ (๋ณดํต ์ฒซ ์ค)
lines = text.split('\n')
sections['title'] = lines[0] if lines else ''
# ํฌ์์๊ฒฌ ์ถ์ถ (๋งค์, ๋งค๋, ์ค๋ฆฝ ๋ฑ)
opinion_pattern = r'(ํฌ์์๊ฒฌ|ํฌ์๋ฑ๊ธ|Investment Rating)[:\s]*(๋งค์|๋งค๋|์ค๋ฆฝ|BUY|SELL|HOLD)'
opinion_match = re.search(opinion_pattern, text, re.IGNORECASE)
if opinion_match:
sections['investment_opinion'] = opinion_match.group(2)
# ๋ชฉํ์ฃผ๊ฐ ์ถ์ถ
target_pattern = r'๋ชฉํ์ฃผ๊ฐ[:\s]*([0-9,]+)\s*์'
target_match = re.search(target_pattern, text)
if target_match:
sections['target_price'] = target_match.group(1)
# ํต์ฌ ์ฌ๋ฌด ์งํ ์ถ์ถ
financial_keywords = ['์์
์ด์ต', '๋งค์ถ์ก', 'EPS', 'PER', 'PBR', 'ROE']
financial_sentences = []
for line in lines:
if any(keyword in line for keyword in financial_keywords):
financial_sentences.append(line.strip())
sections['financial_analysis'] = '\n'.join(financial_sentences)
return sections
# ์ฌ์ฉ ์์
structured_data = preprocess_report(report_text)
print("ํฌ์์๊ฒฌ:", structured_data['investment_opinion'])
print("๋ชฉํ์ฃผ๊ฐ:", structured_data['target_price'])
๐ Step 3: AI ๋ชจ๋ธ์ ํ์ฉํ ์์ฝ ์์ฑ
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก AI๋ฅผ ํ์ฉํ ์ฐจ๋ก์ผ! ์ฌ๊ธฐ์๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์๊ฐํ ๊ฒ. ๐ค
๋ฐฉ๋ฒ 1: OpenAI GPT API ํ์ฉ (์ถ์ฒ! ๐)
import openai
from typing import Dict
# API ํค ์ค์ (์ค์ ์ฌ์ฉ ์ ํ๊ฒฝ๋ณ์๋ก ๊ด๋ฆฌํ์ธ์!)
openai.api_key = "your-api-key-here"
def summarize_with_gpt(report_text: str, max_length: int = 500) -> Dict[str, str]:
"""GPT๋ฅผ ์ฌ์ฉํด ํฌ์ ๋ฆฌํฌํธ๋ฅผ ์์ฝํ๋ ํจ์"""
# ํ๋กฌํํธ ์์ง๋์ด๋ง์ด ํต์ฌ!
prompt = f"""
๋ค์์ ํฌ์ ๋ฆฌํฌํธ์ ๋ด์ฉ์
๋๋ค. ์ด ๋ฆฌํฌํธ๋ฅผ ๋ถ์ํ์ฌ ๋ค์ ํญ๋ชฉ๋ค์ ์ถ์ถํด์ฃผ์ธ์:
1. ํต์ฌ ํฌ์ ํฌ์ธํธ (3๊ฐ์ง)
2. ํฌ์์๊ฒฌ ๋ฐ ๋ชฉํ์ฃผ๊ฐ
3. ์ฃผ์ ์ฌ๋ฌด ์ ๋ง
4. ๋ฆฌ์คํฌ ์์ธ
๋ฆฌํฌํธ ๋ด์ฉ:
{report_text[:4000]} # GPT ํ ํฐ ์ ํ ๊ณ ๋ ค
์ ๋ด์ฉ์ {max_length}์ ์ด๋ด๋ก ๋ช
ํํ๊ณ ๊ฐ๊ฒฐํ๊ฒ ์์ฝํด์ฃผ์ธ์.
"""
try:
response = openai.ChatCompletion.create(
model="gpt-4", # ๋๋ "gpt-3.5-turbo"
messages=[
{"role": "system", "content": "๋น์ ์ ์ ๋ฌธ ๊ธ์ต ์ ๋๋ฆฌ์คํธ์
๋๋ค. ํฌ์ ๋ฆฌํฌํธ๋ฅผ ์ ํํ๊ณ ๊ฐ๊ฒฐํ๊ฒ ์์ฝํ๋ ๊ฒ์ด ํน๊ธฐ์
๋๋ค."},
{"role": "user", "content": prompt}
],
temperature=0.3, # ๋ฎ์์๋ก ์ผ๊ด์ฑ ์๋ ๋ต๋ณ
max_tokens=1000
)
summary = response.choices[0].message.content
# ๊ฒฐ๊ณผ๋ฅผ ๊ตฌ์กฐํ
result = {
'summary': summary,
'model': 'gpt-4',
'tokens_used': response.usage.total_tokens
}
return result
except Exception as e:
print(f"์ค๋ฅ ๋ฐ์: {e}")
return {'summary': '', 'error': str(e)}
# ์ฌ์ฉ ์์
summary_result = summarize_with_gpt(report_text)
print("=== AI ์์ฝ ๊ฒฐ๊ณผ ===")
print(summary_result['summary'])
print(f"\n์ฌ์ฉ๋ ํ ํฐ: {summary_result['tokens_used']}")
โ ๏ธ ๋น์ฉ ๊ด๋ จ ์ฃผ์์ฌํญ
OpenAI API๋ ์ ๋ฃ์ผ. GPT-4๋ ํ ํฐ๋น ๋น์ฉ์ด ์ข ์์ด์, ๋ง์ ๋ฆฌํฌํธ๋ฅผ ์ฒ๋ฆฌํ๋ฉด ๋น์ฉ์ด ์์ผ ์ ์์ด.
๋น์ฉ ์ ๊ฐ ํ:
โข GPT-3.5-turbo ์ฌ์ฉ (GPT-4๋ณด๋ค 10๋ฐฐ ์ ๋ ด)
โข ์
๋ ฅ ํ
์คํธ๋ฅผ ๋ฏธ๋ฆฌ ์์ถ (ํต์ฌ ๋ถ๋ถ๋ง ์ ๋ฌ)
โข ๋ฐฐ์น ์ฒ๋ฆฌ๋ก ํจ์จ์ฑ ๋์ด๊ธฐ
โข ์บ์ฑ ํ์ฉ (๊ฐ์ ๋ฆฌํฌํธ ์ฌ์์ฝ ๋ฐฉ์ง)
ํ ๋ฌ์ 100๊ฐ ๋ฆฌํฌํธ ์ ๋ ์ฒ๋ฆฌํ๋ค๋ฉด ๋๋ต $10~30 ์ ๋ ์์ํ๋ฉด ๋ผ!
๋ฐฉ๋ฒ 2: ์คํ์์ค ๋ชจ๋ธ ํ์ฉ (๋ฌด๋ฃ! ๐ฐ)
๋น์ฉ์ด ๋ถ๋ด๋๋ค๋ฉด ํ๊น ํ์ด์ค์ ์คํ์์ค ๋ชจ๋ธ์ ์ฌ์ฉํ ์๋ ์์ด. ์ฑ๋ฅ์ GPT๋ณด๋ค ์กฐ๊ธ ๋จ์ด์ง์ง๋ง, ๋ฌด๋ฃ๊ณ ์์ฒด ์๋ฒ์์ ๋๋ฆด ์ ์๋ค๋ ์ฅ์ ์ด ์์ง!
from transformers import pipeline
import torch
def summarize_with_opensource(text: str, max_length: int = 500) -> str:
"""์คํ์์ค ๋ชจ๋ธ๋ก ์์ฝํ๋ ํจ์"""
# GPU ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ
device = 0 if torch.cuda.is_available() else -1
# ํ๊ตญ์ด ์์ฝ ๋ชจ๋ธ ๋ก๋
# ์: "eenzeenee/t5-base-korean-summarization"
summarizer = pipeline(
"summarization",
model="facebook/bart-large-cnn", # ์์ด ๋ชจ๋ธ
device=device
)
# ํ
์คํธ๊ฐ ๋๋ฌด ๊ธธ๋ฉด ์ฒญํฌ๋ก ๋๋๊ธฐ
max_chunk_length = 1024
chunks = [text[i:i+max_chunk_length]
for i in range(0, len(text), max_chunk_length)]
summaries = []
for chunk in chunks[:3]: # ์ฒ์ 3๊ฐ ์ฒญํฌ๋ง ์ฒ๋ฆฌ
try:
summary = summarizer(
chunk,
max_length=max_length,
min_length=50,
do_sample=False
)
summaries.append(summary[0]['summary_text'])
except Exception as e:
print(f"์ฒญํฌ ์ฒ๋ฆฌ ์ค ์ค๋ฅ: {e}")
continue
# ๋ชจ๋ ์์ฝ์ ํฉ์น๊ธฐ
final_summary = " ".join(summaries)
return final_summary
# ์ฌ์ฉ ์์
opensource_summary = summarize_with_opensource(report_text)
print("=== ์คํ์์ค ๋ชจ๋ธ ์์ฝ ===")
print(opensource_summary)
๐ฐ๐ท ํ๊ตญ์ด ๋ชจ๋ธ ์ถ์ฒ:
ํ๊ตญ์ด ํฌ์ ๋ฆฌํฌํธ๋ฅผ ๋ค๋ฃฌ๋ค๋ฉด ํ๊ตญ์ด์ ํนํ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ๋ ๊ฒ ์ข์:
โข eenzeenee/t5-base-korean-summarization
โข psyche/KoT5-summarization
โข lcw99/t5-base-korean-text-summary
์ด๋ฐ ๋ชจ๋ธ๋ค์ ํ๊ตญ์ด ๋ฌธ๋ฒ๊ณผ ๊ธ์ต ์ฉ์ด๋ฅผ ๋ ์ ์ดํดํด!
๐ Step 4: ํต์ฌ ์ ๋ณด ์ถ์ถ ๋ฐ ๊ตฌ์กฐํ
์์ฝ๋ง์ผ๋ก๋ ๋ถ์กฑํด. ํฌ์ ํ๋จ์ ํ์ํ ๊ตฌ์ฒด์ ์ธ ์ ๋ณด๋ค์ ๋ฐ๋ก ๋ฝ์๋ด์ผ ํด! ๐ฏ
import re
from datetime import datetime
from typing import Dict, List
class InvestmentReportAnalyzer:
"""ํฌ์ ๋ฆฌํฌํธ ๋ถ์ ํด๋์ค"""
def __init__(self, text: str):
self.text = text
self.analysis = {}
def extract_key_metrics(self) -> Dict[str, any]:
"""ํต์ฌ ์งํ ์ถ์ถ"""
metrics = {}
# ๋ชฉํ์ฃผ๊ฐ ์ถ์ถ
target_price_pattern = r'๋ชฉํ์ฃผ?๊ฐ[:\s]*([0-9,]+)\s*์'
match = re.search(target_price_pattern, self.text)
if match:
metrics['target_price'] = int(match.group(1).replace(',', ''))
# ํ์ฌ์ฃผ๊ฐ ์ถ์ถ
current_price_pattern = r'ํ์ฌ์ฃผ?๊ฐ[:\s]*([0-9,]+)\s*์'
match = re.search(current_price_pattern, self.text)
if match:
metrics['current_price'] = int(match.group(1).replace(',', ''))
# ์์น์ฌ๋ ฅ ๊ณ์ฐ
if 'target_price' in metrics and 'current_price' in metrics:
upside = ((metrics['target_price'] - metrics['current_price'])
/ metrics['current_price'] * 100)
metrics['upside_potential'] = round(upside, 2)
# EPS ์ถ์ถ
eps_pattern = r'EPS[:\s]*([0-9,]+)\s*์'
match = re.search(eps_pattern, self.text)
if match:
metrics['eps'] = int(match.group(1).replace(',', ''))
# PER ์ถ์ถ
per_pattern = r'PER[:\s]*([0-9.]+)\s*๋ฐฐ'
match = re.search(per_pattern, self.text)
if match:
metrics['per'] = float(match.group(1))
return metrics
def extract_investment_opinion(self) -> Dict[str, str]:
"""ํฌ์์๊ฒฌ ์ถ์ถ"""
opinion_data = {}
# ํฌ์์๊ฒฌ ๋งคํ
opinion_map = {
'๋งค์': 'BUY',
'BUY': 'BUY',
'์ ๊ทน๋งค์': 'STRONG_BUY',
'์ค๋ฆฝ': 'HOLD',
'HOLD': 'HOLD',
'๋งค๋': 'SELL',
'SELL': 'SELL'
}
# ํฌ์์๊ฒฌ ์ฐพ๊ธฐ
for korean, english in opinion_map.items():
if korean in self.text:
opinion_data['opinion'] = english
opinion_data['opinion_kr'] = korean
break
# ์ด์ ์๊ฒฌ๊ณผ ๋น๊ต
change_pattern = r'(์ํฅ|ํํฅ|์ ์ง)'
match = re.search(change_pattern, self.text)
if match:
opinion_data['change'] = match.group(1)
return opinion_data
def extract_financial_forecast(self) -> List[Dict]:
"""์ฌ๋ฌด ์ ๋ง ์ถ์ถ"""
forecasts = []
# ์ฐ๋๋ณ ์ค์ ์ ๋ง ํจํด
year_pattern = r'(20\d{2})๋
.*?๋งค์ถ.*?([0-9,]+).*?์์
์ด์ต.*?([0-9,]+)'
matches = re.finditer(year_pattern, self.text)
for match in matches:
forecast = {
'year': match.group(1),
'revenue': match.group(2).replace(',', ''),
'operating_profit': match.group(3).replace(',', '')
}
forecasts.append(forecast)
return forecasts
def extract_key_points(self) -> List[str]:
"""ํต์ฌ ํฌ์ธํธ ์ถ์ถ"""
key_points = []
# "ํต์ฌ", "ํฌ์ธํธ", "์ฃผ๋ชฉ" ๊ฐ์ ํค์๋๊ฐ ์๋ ๋ฌธ์ฅ ์ฐพ๊ธฐ
keywords = ['ํต์ฌ', 'ํฌ์ธํธ', '์ฃผ๋ชฉ', '์ฃผ์', 'ํน์ง']
sentences = self.text.split('.')
for sentence in sentences:
if any(keyword in sentence for keyword in keywords):
cleaned = sentence.strip()
if len(cleaned) > 20: # ๋๋ฌด ์งง์ ๋ฌธ์ฅ ์ ์ธ
key_points.append(cleaned)
return key_points[:5] # ์์ 5๊ฐ๋ง
def analyze(self) -> Dict[str, any]:
"""์ ์ฒด ๋ถ์ ์คํ"""
self.analysis = {
'metrics': self.extract_key_metrics(),
'opinion': self.extract_investment_opinion(),
'forecast': self.extract_financial_forecast(),
'key_points': self.extract_key_points(),
'analyzed_at': datetime.now().isoformat()
}
return self.analysis
# ์ฌ์ฉ ์์
analyzer = InvestmentReportAnalyzer(report_text)
analysis_result = analyzer.analyze()
print("=== ๋ถ์ ๊ฒฐ๊ณผ ===")
print(f"๋ชฉํ์ฃผ๊ฐ: {analysis_result['metrics'].get('target_price', 'N/A'):,}์")
print(f"์์น์ฌ๋ ฅ: {analysis_result['metrics'].get('upside_potential', 'N/A')}%")
print(f"ํฌ์์๊ฒฌ: {analysis_result['opinion'].get('opinion_kr', 'N/A')}")
print("\nํต์ฌ ํฌ์ธํธ:")
for i, point in enumerate(analysis_result['key_points'], 1):
print(f"{i}. {point}")
๐ ์๋ํ ์์คํ ๊ตฌ์ถํ๊ธฐ
์ด์ ์ด ๋ชจ๋ ๊ฑธ ์๋์ผ๋ก ๋์๊ฐ๊ฒ ๋ง๋ค์ด๋ณด์! ๋งค์ผ ์์นจ ์๋์ผ๋ก ์ ๋ฆฌํฌํธ๋ฅผ ์์งํ๊ณ ๋ถ์ํด์ ์ด๋ฉ์ผ์ด๋ ์ฌ๋์ผ๋ก ๋ณด๋ด์ฃผ๋ ์์คํ ์ ๋ง๋ค ๊ฑฐ์ผ. ๐คโ๏ธ
๐ ์ค์ผ์ค๋ง ์ค์
import schedule
import time
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class AutomatedReportAnalyzer:
"""์๋ํ๋ ๋ฆฌํฌํธ ๋ถ์ ์์คํ
"""
def __init__(self, email_config: Dict[str, str]):
self.email_config = email_config
self.reports_processed = 0
def collect_new_reports(self) -> List[str]:
"""์๋ก์ด ๋ฆฌํฌํธ ์์ง"""
# ์ค์ ๋ก๋ ์น ํฌ๋กค๋ง์ด๋ API ํธ์ถ๋ก ๊ตฌํ
# ์ฌ๊ธฐ์๋ ์์๋ก ํ์ผ ์์คํ
์ฒดํฌ
import os
reports_dir = "./reports"
new_reports = []
if os.path.exists(reports_dir):
for filename in os.listdir(reports_dir):
if filename.endswith('.pdf'):
filepath = os.path.join(reports_dir, filename)
# ์ค๋ ์์ฑ๋ ํ์ผ๋ง
if self._is_today_file(filepath):
new_reports.append(filepath)
return new_reports
def _is_today_file(self, filepath: str) -> bool:
"""ํ์ผ์ด ์ค๋ ์์ฑ๋์๋์ง ํ์ธ"""
import os
from datetime import date
file_time = os.path.getmtime(filepath)
file_date = datetime.fromtimestamp(file_time).date()
return file_date == date.today()
def process_report(self, pdf_path: str) -> Dict[str, any]:
"""๋ฆฌํฌํธ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ"""
print(f"์ฒ๋ฆฌ ์ค: {pdf_path}")
# 1. PDF์์ ํ
์คํธ ์ถ์ถ
text = extract_text_from_pdf(pdf_path)
# 2. ์ ์ฒ๋ฆฌ
structured = preprocess_report(text)
# 3. AI ์์ฝ
summary = summarize_with_gpt(text, max_length=300)
# 4. ํต์ฌ ์ ๋ณด ์ถ์ถ
analyzer = InvestmentReportAnalyzer(text)
analysis = analyzer.analyze()
# ๊ฒฐ๊ณผ ํตํฉ
result = {
'filename': pdf_path.split('/')[-1],
'summary': summary['summary'],
'analysis': analysis,
'processed_at': datetime.now().isoformat()
}
return result
def send_email_report(self, results: List[Dict]):
"""์ด๋ฉ์ผ๋ก ๊ฒฐ๊ณผ ์ ์ก"""
if not results:
print("์ ์กํ ๋ฆฌํฌํธ๊ฐ ์์ต๋๋ค.")
return
# ์ด๋ฉ์ผ ๋ด์ฉ ์์ฑ
html_content = self._generate_email_html(results)
# ์ด๋ฉ์ผ ์ค์
msg = MIMEMultipart('alternative')
msg['Subject'] = f"๐ ํฌ์ ๋ฆฌํฌํธ ์์ฝ - {datetime.now().strftime('%Y-%m-%d')}"
msg['From'] = self.email_config['from']
msg['To'] = self.email_config['to']
html_part = MIMEText(html_content, 'html')
msg.attach(html_part)
# ์ด๋ฉ์ผ ์ ์ก
try:
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.login(
self.email_config['username'],
self.email_config['password']
)
server.send_message(msg)
print("โ
์ด๋ฉ์ผ ์ ์ก ์๋ฃ!")
except Exception as e:
print(f"โ ์ด๋ฉ์ผ ์ ์ก ์คํจ: {e}")
def _generate_email_html(self, results: List[Dict]) -> str:
"""์ด๋ฉ์ผ HTML ์์ฑ"""
html = f"""
<html>
<head>
<style>.tx-header{background:linear-gradient(135deg,#f5f7fa 0%,#e8ecf1 100%);
border-radius:16px;
padding:40px 30px;
text-align:center;
margin-bottom:40px;
box-shadow:0 4px 15px rgba(0,0,0,0.08)}
.tx-section{margin:50px 0;
line-height:1.9}
.tx-highlight{background:linear-gradient(120deg,#fef5e7 0%,#fdebd0 100%);
padding:25px;
border-radius:12px;
margin:30px 0;
border-left:5px solid #f39c12}
.tx-point-box{background:#f8f9fa;
padding:20px;
border-radius:10px;
margin:25px 0;
border-left:4px solid #3498db}
.tx-tip{background:linear-gradient(135deg,#e8f5e9 0%,#c8e6c9 100%);
padding:20px;
border-radius:10px;
margin:25px 0}
.tx-warning{background:linear-gradient(135deg,#fff3e0 0%,#ffe0b2 100%);
padding:20px;
border-radius:10px;
margin:25px 0}
.tx-table-wrap{overflow-x:auto;
margin:25px 0}
.tx-table{width:100%;
border-collapse:collapse;
background:white;
border-radius:8px;
overflow:hidden}
.tx-table th{background:#34495e;
color:white;
padding:15px;
text-align:left}
.tx-table td{padding:12px 15px;
border-bottom:1px solid #ecf0f1}
.tx-step{background:white;
padding:20px;
margin:20px 0;
border-radius:10px;
box-shadow:0 2px 8px rgba(0,0,0,0.06)}
.tx-icon-text{display:inline-block;
margin:8px 15px 8px 0}
h2{color:#2c3e50;
padding:15px 20px;
background:linear-gradient(120deg,#e3f2fd 0%,#bbdefb 100%);
border-radius:10px;
margin:40px 0 25px 0;
border-left:6px solid #2196f3}
h3{color:#34495e;
padding:12px 18px;
background:linear-gradient(120deg,#f3e5f5 0%,#e1bee7 100%);
border-radius:8px;
margin:30px 0 20px 0;
border-left:5px solid #9c27b0}
h4{color:#455a64;
margin:25px 0 15px 0;
padding-left:15px;
border-left:4px solid #90a4ae}</style>
</head>
<body>
<h2>๐ ์ค๋์ ํฌ์ ๋ฆฌํฌํธ ์์ฝ</h2>
<p>์ด {len(results)}๊ฐ์ ๋ฆฌํฌํธ๊ฐ ๋ถ์๋์์ต๋๋ค.</p>
"""
for result in results:
analysis = result['analysis']
metrics = analysis.get('metrics', {})
opinion = analysis.get('opinion', {})
html += f"""
<div class="report">
<div class="title">{result['filename']}</div>
<p><strong>ํฌ์์๊ฒฌ:</strong> {opinion.get('opinion_kr', 'N/A')}</p>
<p><strong>๋ชฉํ์ฃผ๊ฐ:</strong> <span class="metric">{metrics.get('target_price', 'N/A'):,}์</span></p>
<p><strong>์์น์ฌ๋ ฅ:</strong> <span class="metric">{metrics.get('upside_potential', 'N/A')}%</span></p>
<div class="summary">
<strong>์์ฝ:</strong><br>
{result['summary'][:200]}...
</div>
</div>
"""
html += """
</body>
</html>
"""
return html
def daily_job(self):
"""๋งค์ผ ์คํ๋ ์์
"""
print(f"\n{'='*50}")
print(f"๐ค ์๋ ๋ถ์ ์์: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*50}\n")
# 1. ์ ๋ฆฌํฌํธ ์์ง
new_reports = self.collect_new_reports()
print(f"๐ฅ ์๋ก์ด ๋ฆฌํฌํธ {len(new_reports)}๊ฐ ๋ฐ๊ฒฌ")
# 2. ๊ฐ ๋ฆฌํฌํธ ์ฒ๋ฆฌ
results = []
for report_path in new_reports:
try:
result = self.process_report(report_path)
results.append(result)
self.reports_processed += 1
except Exception as e:
print(f"โ ์ฒ๋ฆฌ ์คํจ ({report_path}): {e}")
# 3. ๊ฒฐ๊ณผ ์ด๋ฉ์ผ ์ ์ก
if results:
self.send_email_report(results)
print(f"\nโ
์์
์๋ฃ! (์ด ์ฒ๋ฆฌ: {self.reports_processed}๊ฐ)")
def start_scheduler(self):
"""์ค์ผ์ค๋ฌ ์์"""
# ๋งค์ผ ์ค์ 9์์ ์คํ
schedule.every().day.at("09:00").do(self.daily_job)
# ํ
์คํธ์ฉ: ๋งค 1๋ถ๋ง๋ค ์คํ
# schedule.every(1).minutes.do(self.daily_job)
print("๐ ์ค์ผ์ค๋ฌ ์์!")
print("๋งค์ผ ์ค์ 9์์ ์๋์ผ๋ก ๋ฆฌํฌํธ๋ฅผ ๋ถ์ํฉ๋๋ค.")
while True:
schedule.run_pending()
time.sleep(60) # 1๋ถ๋ง๋ค ์ฒดํฌ
# ์ฌ์ฉ ์์
email_config = {
'from': 'your-email@gmail.com',
'to': 'recipient@gmail.com',
'username': 'your-email@gmail.com',
'password': 'your-app-password' # Gmail ์ฑ ๋น๋ฐ๋ฒํธ
}
analyzer = AutomatedReportAnalyzer(email_config)
# analyzer.start_scheduler() # ์ค์ ์ด์ ์ ์ฃผ์ ํด์
๐ ๋ณด์ ์ฃผ์์ฌํญ
์ด๋ฉ์ผ ๋น๋ฐ๋ฒํธ๋ API ํค ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ ์ ๋ ์ฝ๋์ ์ง์ ๋ฃ์ผ๋ฉด ์ ๋ผ!
์์ ํ ๊ด๋ฆฌ ๋ฐฉ๋ฒ:
โข ํ๊ฒฝ๋ณ์ ์ฌ์ฉ (os.environ.get('API_KEY'))
โข .env ํ์ผ + python-dotenv ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข AWS Secrets Manager, Azure Key Vault ๊ฐ์ ์๋น์ค
โข .gitignore์ ๋ฏผ๊ฐ ์ ๋ณด ํ์ผ ์ถ๊ฐ
ํนํ GitHub์ ์ฌ๋ฆด ๋ ์กฐ์ฌํด์ผ ํด! ๐จ
๐ ์ค์ ํ์ฉ ์ฌ๋ก
์ด๋ก ๊ณผ ์ฝ๋๋ ์ถฉ๋ถํ ๋ดค์ผ๋, ์ด์ ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉ๋๋์ง ์ฌ๋ก๋ฅผ ์ดํด๋ณด์! ๐ผ
์ฌ๋ก 1: ๊ฐ์ธ ํฌ์์ A์จ์ ํ๋ฃจ
Before (AI ๋์
์ ) ๐ฐ
์ค์ 7์: ์ถ๊ทผ ์ค๋นํ๋ฉด์ ์ฆ๊ถ์ฌ ์ฑ 5๊ฐ ํ์ธ
์ค์ 8์: ์งํ์ฒ ์์ ๋ฆฌํฌํธ 3๊ฐ ๋ค์ด๋ก๋, ์ ๋ชฉ๋ง ํ์ธ
์ ์ฌ์๊ฐ: ๋ฆฌํฌํธ 1๊ฐ ์ฝ๊ธฐ ์์, 20ํ์ด์ง ์ค 5ํ์ด์ง ์ฝ๊ณ ํฌ๊ธฐ
ํด๊ทผ ํ: ํผ๊ณคํด์ ํฌ์ ๊ณต๋ถ๋ ๋ค์์ผ๋ก ๋ฏธ๋ฃธ
์ฃผ๋ง: ๋ฐ๋ฆฐ ๋ฆฌํฌํธ 10๊ฐ... ๊ฒฐ๊ตญ ๋์ถฉ ํ์ด๋ณด๊ธฐ๋ง
๊ฒฐ๊ณผ: ์ค์ํ ํฌ์ ๊ธฐํ ๋์นจ, ์คํธ๋ ์ค ์ฆ๊ฐ ๐
After (AI ๋์
ํ) ๐
์ค์ 7์: ์ผ์ด๋์๋ง์ ์ด๋ฉ์ผ ํ์ธ
โ AI๊ฐ ๋ฐค์ ๋ถ์ํ 5๊ฐ ๋ฆฌํฌํธ ์์ฝ๋ณธ ๋์ฐฉ!
์ค์ 7์ 10๋ถ: ํต์ฌ ๋ด์ฉ๋ง 5๋ถ ๋ง์ ํ์
โ "์ผ์ฑ์ ์ ๋ชฉํ์ฃผ๊ฐ ์ํฅ, ์์น์ฌ๋ ฅ 15%"
โ "SKํ์ด๋์ค ์ค์ ๊ฐ์ ์ ๋ง, ๋งค์ ์๊ฒฌ ์ ์ง"
์ค์ 7์ 15๋ถ: ๊ด์ฌ ์ข
๋ชฉ 2๊ฐ ์ ์ , ์ถ๊ฐ ์กฐ์ฌ ๊ณํ
์ ์ฌ์๊ฐ: ์ ํํ ์ข
๋ชฉ๋ง ์์ธ ๋ฆฌํฌํธ ํ์ธ
ํด๊ทผ ํ: ์ฌ์ ๋กญ๊ฒ ํฌ์ ์ ๋ต ์๋ฆฝ
๊ฒฐ๊ณผ: ์๊ฐ ์ ์ฝ 90%, ํฌ์ ์ฑ๊ณผ ํฅ์, ์คํธ๋ ์ค ๊ฐ์! ๐โจ
์ฌ๋ก 2: ์ค์ ์์ฐ์ด์ฉ์ฌ B์ฌ
๋์
๋ฐฐ๊ฒฝ:
โข ์ ๋๋ฆฌ์คํธ 3๋ช
์ด ํ๋ฃจ ํ๊ท 30๊ฐ ๋ฆฌํฌํธ ๊ฒํ
โข ์ธ๋ ฅ ๋ถ์กฑ์ผ๋ก ์ค์ํ ์ ๋ณด ๋์น๋ ๊ฒฝ์ฐ ๋ฐ์
โข ๋ฆฌํฌํธ ์ ๋ฆฌ์๋ง ํ๋ฃจ 2~3์๊ฐ ์์
AI ์์คํ
๋์
ํ:
โข ํ๋ฃจ 100๊ฐ ์ด์ ๋ฆฌํฌํธ ์๋ ๋ถ์
โข ํต์ฌ ์ ๋ณด๋ง ์ถ์ถํ์ฌ ๋์๋ณด๋์ ํ์
โข ์ ๋๋ฆฌ์คํธ๋ค์ ๋ถ์๊ณผ ์์ฌ๊ฒฐ์ ์ ์ง์ค
โข ๋ฆฌํฌํธ ๊ฒํ ์๊ฐ 70% ๋จ์ถ
์ถ๊ฐ ๊ธฐ๋ฅ:
โข ์ข
๋ชฉ๋ณ ๋ฆฌํฌํธ ํ์คํ ๋ฆฌ ์๋ ๊ด๋ฆฌ
โข ๋ชฉํ์ฃผ๊ฐ ๋ณ๋ ์ถ์ด ๊ทธ๋ํ ์๋ ์์ฑ
โข ํฌ์์๊ฒฌ ๋ณ๊ฒฝ ์ ์๋ฆผ ๋ฐ์ก
โข ๊ฒฝ์์ฌ ๋ฆฌํฌํธ์ ์๋ ๋น๊ต ๋ถ์
ROI: 6๊ฐ์ ๋ง์ ์์คํ
๊ตฌ์ถ ๋น์ฉ ํ์! ๐ฐ
์ฌ๋ก 3: ์ฌ๋ฅ๋ท์์ ์๋น์ค๋ก ์ ๊ณต
์ด๋ฐ AI ๋ถ์ ์์คํ
์ ์ง์ ๋ง๋ค๊ธฐ ์ด๋ ต๋ค๋ฉด? ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ์ ์ ์์ด! ๐ฏ
์ฌ๋ฅ๋ท์์ ์ฐพ์ ์ ์๋ ์๋น์ค:
โข AI ํฌ์ ๋ฆฌํฌํธ ๋ถ์ ์์คํ
๊ฐ๋ฐ
โข ๋ง์ถคํ ์๋ํ ์คํฌ๋ฆฝํธ ์ ์
โข ํฌ๋กค๋ง ๋ฐ ๋ฐ์ดํฐ ์์ง ์๋ํ
โข ํฌ์ ๋์๋ณด๋ ๊ตฌ์ถ
โข API ์ฐ๋ ๋ฐ ์์คํ
ํตํฉ
์ค์ ๋ก ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ฌ๋ฅ๋ท์์ ์ด๋ฐ ํ๋ก์ ํธ๋ฅผ ์งํํ๊ณ ์์ด. ํผ์ ๊ณ ๋ฏผํ์ง ๋ง๊ณ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ! ๐ช
๐จ ๋์๋ณด๋ ๋ง๋ค๊ธฐ
๋ถ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ธฐ ์ข๊ฒ ์๊ฐํํ๋ ๊ฒ๋ ์ค์ํด! ๊ฐ๋จํ ์น ๋์๋ณด๋๋ฅผ ๋ง๋ค์ด๋ณด์. ๐ฅ๏ธ
Streamlit์ผ๋ก ๋น ๋ฅด๊ฒ ๋ง๋ค๊ธฐ
import streamlit as st
import pandas as pd
import plotly.graph_objects as go
from datetime import datetime, timedelta
# ํ์ด์ง ์ค์
st.set_page_config(
page_title="ํฌ์ ๋ฆฌํฌํธ ๋ถ์ ๋์๋ณด๋",
page_icon="๐",
layout="wide"
)
# ํ์ดํ
st.title("๐ AI ํฌ์ ๋ฆฌํฌํธ ๋ถ์ ๋์๋ณด๋")
st.markdown("---")
# ์ฌ์ด๋๋ฐ
with st.sidebar:
st.header("โ๏ธ ์ค์ ")
# ๋ ์ง ๋ฒ์ ์ ํ
date_range = st.date_input(
"๋ถ์ ๊ธฐ๊ฐ",
value=(datetime.now() - timedelta(days=7), datetime.now())
)
# ์ข
๋ชฉ ์ ํ
stocks = st.multiselect(
"์ข
๋ชฉ ์ ํ",
["์ผ์ฑ์ ์", "SKํ์ด๋์ค", "NAVER", "์นด์นด์ค", "LG์๋์ง์๋ฃจ์
"],
default=["์ผ์ฑ์ ์", "SKํ์ด๋์ค"]
)
# ํฌ์์๊ฒฌ ํํฐ
opinions = st.multiselect(
"ํฌ์์๊ฒฌ",
["๋งค์", "์ค๋ฆฝ", "๋งค๋"],
default=["๋งค์", "์ค๋ฆฝ"]
)
# ๋ฉ์ธ ์ปจํ
์ธ
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
label="๐ ๋ถ์๋ ๋ฆฌํฌํธ",
value="127",
delta="15"
)
with col2:
st.metric(
label="๐ฏ ํ๊ท ๋ชฉํ์ฃผ๊ฐ ์์น๋ฅ ",
value="12.5%",
delta="2.3%"
)
with col3:
st.metric(
label="๐ ๋งค์ ์๊ฒฌ ๋น์จ",
value="68%",
delta="5%"
)
st.markdown("---")
# ๋ฆฌํฌํธ ๋ชฉ๋ก
st.subheader("๐ ์ต๊ทผ ๋ถ์ ๋ฆฌํฌํธ")
# ์ํ ๋ฐ์ดํฐ
data = {
'์ข
๋ชฉ': ['์ผ์ฑ์ ์', 'SKํ์ด๋์ค', 'NAVER', '์นด์นด์ค'],
'์ฆ๊ถ์ฌ': ['ํ๊ตญํฌ์์ฆ๊ถ', 'NHํฌ์์ฆ๊ถ', '๋ฏธ๋์์
์ฆ๊ถ', 'KB์ฆ๊ถ'],
'ํฌ์์๊ฒฌ': ['๋งค์', '๋งค์', '์ค๋ฆฝ', '๋งค์'],
'๋ชฉํ์ฃผ๊ฐ': ['85,000์', '145,000์', '250,000์', '65,000์'],
'์์น์ฌ๋ ฅ': ['+15.2%', '+18.5%', '+8.3%', '+12.7%'],
'๋ฐํ์ผ': ['2024-01-15', '2024-01-15', '2024-01-14', '2024-01-14']
}
df = pd.DataFrame(data)
# ์คํ์ผ ์ ์ฉ
def highlight_opinion(val):
if val == '๋งค์':
return 'background-color: #d4edda; color: #155724'
elif val == '๋งค๋':
return 'background-color: #f8d7da; color: #721c24'
else:
return 'background-color: #fff3cd; color: #856404'
styled_df = df.style.applymap(
highlight_opinion,
subset=['ํฌ์์๊ฒฌ']
)
st.dataframe(styled_df, use_container_width=True)
# ์ฐจํธ
st.markdown("---")
st.subheader("๐ ๋ชฉํ์ฃผ๊ฐ ์ถ์ด")
# ์ํ ์ฐจํธ ๋ฐ์ดํฐ
chart_data = pd.DataFrame({
'๋ ์ง': pd.date_range(start='2024-01-01', periods=15, freq='D'),
'์ผ์ฑ์ ์': [75000, 76000, 77000, 78000, 79000, 80000, 81000,
82000, 81500, 82500, 83000, 83500, 84000, 84500, 85000],
'SKํ์ด๋์ค': [120000, 122000, 125000, 127000, 130000, 132000, 135000,
137000, 138000, 140000, 141000, 142000, 143000, 144000, 145000]
})
fig = go.Figure()
for stock in ['์ผ์ฑ์ ์', 'SKํ์ด๋์ค']:
fig.add_trace(go.Scatter(
x=chart_data['๋ ์ง'],
y=chart_data[stock],
mode='lines+markers',
name=stock,
line=dict(width=3)
))
fig.update_layout(
title="์ข
๋ชฉ๋ณ ๋ชฉํ์ฃผ๊ฐ ๋ณ๋ ์ถ์ด",
xaxis_title="๋ ์ง",
yaxis_title="๋ชฉํ์ฃผ๊ฐ (์)",
hovermode='x unified',
height=400
)
st.plotly_chart(fig, use_container_width=True)
# ์์ฝ ์น์
st.markdown("---")
st.subheader("๐ก AI ์์ฝ")
with st.expander("์ผ์ฑ์ ์ - ํ๊ตญํฌ์์ฆ๊ถ (2024-01-15)", expanded=True):
st.markdown("""
**ํฌ์์๊ฒฌ:** ๋งค์ (์ ์ง)
**๋ชฉํ์ฃผ๊ฐ:** 85,000์ (์ํฅ)
**ํต์ฌ ํฌ์ธํธ:**
1. ๋ฉ๋ชจ๋ฆฌ ๋ฐ๋์ฒด ๊ฐ๊ฒฉ ์์น์ธ ์ง์ ์ ๋ง
2. 2024๋
1๋ถ๊ธฐ ์์
์ด์ต 4.5์กฐ์ ์์ (์ ๋
๋๋น 300% ์ฆ๊ฐ)
3. HBM3 ์์ ์ฆ๊ฐ๋ก ๊ณ ๋ถ๊ฐ ์ ํ ๋น์ค ํ๋
**๋ฆฌ์คํฌ:**
- ์ค๊ตญ ๊ฒฝ๊ธฐ ๋ํ์ ๋ฐ๋ฅธ ์์ ๊ฐ์ ๊ฐ๋ฅ์ฑ
- ํ์จ ๋ณ๋์ฑ
""")
# ํธํฐ
st.markdown("---")
st.caption("๐ค Powered by AI Report Analyzer | ๋ง์ง๋ง ์
๋ฐ์ดํธ: " +
datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
์ด ์ฝ๋๋ฅผ ์คํํ๋ฉด ์ด๋ฐ ๋ฉ์ง ๋์๋ณด๋๊ฐ ๋ง๋ค์ด์ ธ:
โข ์ค์๊ฐ์ผ๋ก ์
๋ฐ์ดํธ๋๋ ๋ฆฌํฌํธ ๋ชฉ๋ก
โข ์ธํฐ๋ํฐ๋ธํ ์ฐจํธ์ ๊ทธ๋ํ
โข ํํฐ๋ง๊ณผ ๊ฒ์ ๊ธฐ๋ฅ
โข ๋ชจ๋ฐ์ผ์์๋ ์ ๋ณด์ด๋ ๋ฐ์ํ ๋์์ธ
Streamlit์ ์ ๋ง ๊ฐ๋จํ๋ฉด์๋ ๊ฐ๋ ฅํด. Python๋ง ์๋ฉด ๋๊ตฌ๋ ๋ฉ์ง ๋์๋ณด๋๋ฅผ ๋ง๋ค ์ ์์ด! ๐จ
โก ์ฑ๋ฅ ์ต์ ํ ํ
์์คํ ์ ๋ง๋ค์๋ค๋ฉด ์ด์ ๋ ๋น ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ๋ง๋ค์ด์ผ์ง! ์ค์ ์์ ๊ผญ ํ์ํ ์ต์ ํ ๊ธฐ๋ฒ๋ค์ ์๋ ค์ค๊ฒ. ๐
1. ์บ์ฑ ํ์ฉํ๊ธฐ
import hashlib
import json
from functools import lru_cache
import redis
class ReportCache:
"""๋ฆฌํฌํธ ๋ถ์ ๊ฒฐ๊ณผ ์บ์ฑ"""
def __init__(self, use_redis=False):
self.use_redis = use_redis
if use_redis:
self.redis_client = redis.Redis(
host='localhost',
port=6379,
decode_responses=True
)
def get_cache_key(self, text: str) -> str:
"""ํ
์คํธ์ ํด์๊ฐ์ผ๋ก ์บ์ ํค ์์ฑ"""
return hashlib.md5(text.encode()).hexdigest()
def get(self, text: str):
"""์บ์์์ ๊ฒฐ๊ณผ ๊ฐ์ ธ์ค๊ธฐ"""
key = self.get_cache_key(text)
if self.use_redis:
cached = self.redis_client.get(key)
if cached:
return json.loads(cached)
else:
# ๋ฉ๋ชจ๋ฆฌ ์บ์ (๊ฐ๋จํ ๋์
๋๋ฆฌ)
if hasattr(self, '_cache') and key in self._cache:
return self._cache[key]
return None
def set(self, text: str, result: dict, expire: int = 86400):
"""๊ฒฐ๊ณผ๋ฅผ ์บ์์ ์ ์ฅ (๊ธฐ๋ณธ 24์๊ฐ)"""
key = self.get_cache_key(text)
if self.use_redis:
self.redis_client.setex(
key,
expire,
json.dumps(result)
)
else:
if not hasattr(self, '_cache'):
self._cache = {}
self._cache[key] = result
# ์ฌ์ฉ ์์
cache = ReportCache(use_redis=False)
def analyze_with_cache(text: str):
"""์บ์๋ฅผ ํ์ฉํ ๋ถ์"""
# ์บ์ ํ์ธ
cached_result = cache.get(text)
if cached_result:
print("โ
์บ์์์ ๊ฒฐ๊ณผ ๋ก๋!")
return cached_result
# ์บ์ ์์ผ๋ฉด ์๋ก ๋ถ์
print("๐ ์๋ก ๋ถ์ ์ค...")
result = summarize_with_gpt(text)
# ๊ฒฐ๊ณผ ์บ์ฑ
cache.set(text, result)
return result
๐ก ์บ์ฑ ํจ๊ณผ:
๊ฐ์ ๋ฆฌํฌํธ๋ฅผ ๋ค์ ๋ถ์ํ ๋:
โข ๋ถ์ ์๊ฐ: 10์ด โ 0.01์ด (1000๋ฐฐ ๋น ๋ฆ!)
โข API ๋น์ฉ: $0.05 โ $0 (100% ์ ๊ฐ!)
โข ์๋ฒ ๋ถํ: ๋ํญ ๊ฐ์
ํนํ ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๊ฐ์ ๋ฆฌํฌํธ๋ฅผ ์กฐํํ๋ ๊ฒฝ์ฐ ์์ฒญ๋ ํจ๊ณผ๋ฅผ ๋ณผ ์ ์์ด!
2. ๋ฐฐ์น ์ฒ๋ฆฌ
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List
import time
def batch_process_reports(report_paths: List[str], max_workers: int = 5):
"""์ฌ๋ฌ ๋ฆฌํฌํธ๋ฅผ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌ"""
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# ๋ชจ๋ ์์
์ ์ถ
future_to_path = {
executor.submit(process_single_report, path): path
for path in report_paths
}
# ์๋ฃ๋ ์์
๋ถํฐ ๊ฒฐ๊ณผ ์์ง
for future in as_completed(future_to_path):
path = future_to_path[future]
try:
result = future.result()
results.append(result)
print(f"โ
์๋ฃ: {path}")
except Exception as e:
print(f"โ ์คํจ: {path} - {e}")
return results
def process_single_report(path: str):
"""๋จ์ผ ๋ฆฌํฌํธ ์ฒ๋ฆฌ"""
text = extract_text_from_pdf(path)
summary = summarize_with_gpt(text)
return {
'path': path,
'summary': summary,
'processed_at': time.time()
}
# ์ฌ์ฉ ์์
report_files = [
"report1.pdf",
"report2.pdf",
"report3.pdf",
"report4.pdf",
"report5.pdf"
]
start_time = time.time()
results = batch_process_reports(report_files, max_workers=3)
end_time = time.time()
print(f"\n์ด ์ฒ๋ฆฌ ์๊ฐ: {end_time - start_time:.2f}์ด")
print(f"๋ฆฌํฌํธ๋น ํ๊ท : {(end_time - start_time) / len(report_files):.2f}์ด")
3. ํ ์คํธ ์์ถ ๋ฐ ์ฒญํน
๊ธด ๋ฆฌํฌํธ๋ฅผ ์ฒ๋ฆฌํ ๋๋ ํ ์คํธ๋ฅผ ํจ์จ์ ์ผ๋ก ๋๋๋ ๊ฒ ์ค์ํด!
def smart_chunk_text(text: str, max_chunk_size: int = 3000) -> List[str]:
"""ํ
์คํธ๋ฅผ ์๋ฏธ ๋จ์๋ก ์ฒญํน"""
# ๋ฌธ๋จ ๋จ์๋ก ๋ถ๋ฆฌ
paragraphs = text.split('\n\n')
chunks = []
current_chunk = ""
for para in paragraphs:
# ํ์ฌ ์ฒญํฌ์ ์ถ๊ฐํด๋ ํฌ๊ธฐ๊ฐ ๊ด์ฐฎ์ผ๋ฉด
if len(current_chunk) + len(para) < max_chunk_size:
current_chunk += para + "\n\n"
else:
# ํ์ฌ ์ฒญํฌ ์ ์ฅํ๊ณ ์ ์ฒญํฌ ์์
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = para + "\n\n"
# ๋ง์ง๋ง ์ฒญํฌ ์ถ๊ฐ
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
def summarize_long_report(text: str) -> str:
"""๊ธด ๋ฆฌํฌํธ๋ฅผ ์ฒญํฌ๋ณ๋ก ์์ฝ ํ ํตํฉ"""
# 1๋จ๊ณ: ํ
์คํธ๋ฅผ ์ฒญํฌ๋ก ๋๋๊ธฐ
chunks = smart_chunk_text(text, max_chunk_size=3000)
print(f"๐ {len(chunks)}๊ฐ ์ฒญํฌ๋ก ๋ถํ ")
# 2๋จ๊ณ: ๊ฐ ์ฒญํฌ ์์ฝ
chunk_summaries = []
for i, chunk in enumerate(chunks):
print(f"์ฒ๋ฆฌ ์ค: ์ฒญํฌ {i+1}/{len(chunks)}")
summary = summarize_with_gpt(chunk, max_length=200)
chunk_summaries.append(summary['summary'])
# 3๋จ๊ณ: ์์ฝ๋ค์ ๋ค์ ํตํฉ ์์ฝ
combined = "\n\n".join(chunk_summaries)
final_summary = summarize_with_gpt(combined, max_length=500)
return final_summary['summary']
๐ ๋ณด์ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ
์ค์ ์์๋ ์์์น ๋ชปํ ๋ฌธ์ ๋ค์ด ๋ง์ด ๋ฐ์ํด. ๊ฒฌ๊ณ ํ ์์คํ ์ ๋ง๋ค๊ธฐ ์ํ ํ์ ์์๋ค์ ์์๋ณด์! ๐ก๏ธ
์๋ฌ ์ฒ๋ฆฌ ๋ฐ ๋ก๊น
import logging
from datetime import datetime
import traceback
# ๋ก๊ฑฐ ์ค์
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('report_analyzer.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger('ReportAnalyzer')
class RobustReportAnalyzer:
"""์๋ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐํ๋ ๋ถ์๊ธฐ"""
def __init__(self):
self.success_count = 0
self.error_count = 0
self.errors = []
def safe_process(self, pdf_path: str, max_retries: int = 3):
"""์์ ํ ๋ฆฌํฌํธ ์ฒ๋ฆฌ (์ฌ์๋ ํฌํจ)"""
for attempt in range(max_retries):
try:
logger.info(f"์ฒ๋ฆฌ ์์: {pdf_path} (์๋ {attempt + 1}/{max_retries})")
# 1. PDF ์ถ์ถ
text = self._safe_extract_pdf(pdf_path)
if not text:
raise ValueError("ํ
์คํธ ์ถ์ถ ์คํจ")
# 2. ์์ฝ ์์ฑ
summary = self._safe_summarize(text)
if not summary:
raise ValueError("์์ฝ ์์ฑ ์คํจ")
# 3. ๋ถ์
analysis = self._safe_analyze(text)
self.success_count += 1
logger.info(f"โ
์ฒ๋ฆฌ ์ฑ๊ณต: {pdf_path}")
return {
'success': True,
'path': pdf_path,
'summary': summary,
'analysis': analysis
}
except Exception as e:
logger.error(f"โ ์ค๋ฅ ๋ฐ์ (์๋ {attempt + 1}): {str(e)}")
logger.error(traceback.format_exc())
if attempt == max_retries - 1:
# ๋ง์ง๋ง ์๋๋ ์คํจ
self.error_count += 1
self.errors.append({
'path': pdf_path,
'error': str(e),
'timestamp': datetime.now().isoformat()
})
return {
'success': False,
'path': pdf_path,
'error': str(e)
}
# ์ฌ์๋ ์ ๋๊ธฐ
time.sleep(2 ** attempt) # ์ง์ ๋ฐฑ์คํ
def _safe_extract_pdf(self, pdf_path: str) -> str:
"""์์ ํ PDF ์ถ์ถ"""
try:
return extract_text_from_pdf(pdf_path)
except Exception as e:
logger.error(f"PDF ์ถ์ถ ์คํจ: {e}")
raise
def _safe_summarize(self, text: str) -> str:
"""์์ ํ ์์ฝ ์์ฑ"""
try:
# ํ
์คํธ ๊ธธ์ด ์ฒดํฌ
if len(text) < 100:
raise ValueError("ํ
์คํธ๊ฐ ๋๋ฌด ์งง์")
# ์์ฝ ์์ฑ
result = summarize_with_gpt(text)
return result.get('summary', '')
except Exception as e:
logger.error(f"์์ฝ ์์ฑ ์คํจ: {e}")
# ํด๋ฐฑ: ๊ฐ๋จํ ์ถ์ถ ์์ฝ
return self._fallback_summary(text)
def _fallback_summary(self, text: str) -> str:
"""ํด๋ฐฑ ์์ฝ (AI ์คํจ ์)"""
# ์ฒซ 500์๋ง ๋ฐํ
return text[:500] + "..."
def _safe_analyze(self, text: str):
"""์์ ํ ๋ถ์"""
try:
analyzer = InvestmentReportAnalyzer(text)
return analyzer.analyze()
except Exception as e:
logger.error(f"๋ถ์ ์คํจ: {e}")
return {}
def get_statistics(self):
"""์ฒ๋ฆฌ ํต๊ณ"""
total = self.success_count + self.error_count
success_rate = (self.success_count / total * 100) if total > 0 else 0
return {
'total': total,
'success': self.success_count,
'errors': self.error_count,
'success_rate': f"{success_rate:.2f}%"
}
๐ฐ ๋น์ฉ ์ต์ ํ ์ ๋ต
AI API๋ฅผ ์ฌ์ฉํ๋ฉด ๋น์ฉ์ด ๋ฐ์ํด. ๋๋ํ๊ฒ ๋น์ฉ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐ธ
๐ ์ค์ ๋น์ฉ ๋ถ์ (์ 100๊ฐ ๋ฆฌํฌํธ ๊ธฐ์ค)
GPT-4 ์ฌ์ฉ ์:
โข ๋ฆฌํฌํธ๋น ํ๊ท ํ ํฐ: 5,000 (์
๋ ฅ) + 500 (์ถ๋ ฅ)
โข ๋ฆฌํฌํธ๋น ๋น์ฉ: ์ฝ $0.20
โข ์ ์ด ๋น์ฉ: $20
GPT-3.5-turbo ์ฌ์ฉ ์:
โข ๋ฆฌํฌํธ๋น ํ๊ท ํ ํฐ: 5,000 (์
๋ ฅ) + 500 (์ถ๋ ฅ)
โข ๋ฆฌํฌํธ๋น ๋น์ฉ: ์ฝ $0.02
โข ์ ์ด ๋น์ฉ: $2 (90% ์ ๊ฐ!)
์คํ์์ค ๋ชจ๋ธ (์์ฒด ์๋ฒ):
โข API ๋น์ฉ: $0
โข ์๋ฒ ๋น์ฉ: ์ $50~100 (GPU ์ธ์คํด์ค)
โข ์ 100๊ฐ ์ด์ ์ฒ๋ฆฌ ์ ์ ๋ฆฌ
๐ก ์ถ์ฒ ์ ๋ต:
โข ์ผ๋ฐ ์์ฝ: GPT-3.5-turbo
โข ์ค์ ๋ฆฌํฌํธ: GPT-4
โข ๋๋ ์ฒ๋ฆฌ: ์คํ์์ค ๋ชจ๋ธ
ํ ํฐ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
class CostTracker:
"""API ๋น์ฉ ์ถ์ """
def __init__(self):
self.total_tokens = 0
self.total_cost = 0
self.requests = []
def track_request(self, model: str, input_tokens: int, output_tokens: int):
"""์์ฒญ ์ถ์ """
# ๋ชจ๋ธ๋ณ ๊ฐ๊ฒฉ (2024๋
๊ธฐ์ค, 1000 ํ ํฐ๋น)
prices = {
'gpt-4': {'input': 0.03, 'output': 0.06},
'gpt-3.5-turbo': {'input': 0.001, 'output': 0.002}
}
if model in prices:
cost = (
input_tokens / 1000 * prices[model]['input'] +
output_tokens / 1000 * prices[model]['output']
)
self.total_tokens += (input_tokens + output_tokens)
self.total_cost += cost
self.requests.append({
'timestamp': datetime.now(),
'model': model,
'tokens': input_tokens + output_tokens,
'cost': cost
})
def get_summary(self):
"""๋น์ฉ ์์ฝ"""
return {
'total_requests': len(self.requests),
'total_tokens': self.total_tokens,
'total_cost': f"${self.total_cost:.2f}",
'avg_cost_per_request': f"${self.total_cost / len(self.requests):.4f}" if self.requests else "$0"
}
def should_switch_model(self, threshold: float = 10.0) -> bool:
"""๋น์ฉ์ด ์๊ณ๊ฐ์ ๋์ผ๋ฉด ๋ชจ๋ธ ์ ํ ์ ์"""
return self.total_cost > threshold
# ์ฌ์ฉ ์์
tracker = CostTracker()
def summarize_with_tracking(text: str):
"""๋น์ฉ ์ถ์ ์ด ํฌํจ๋ ์์ฝ"""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": text}]
)
# ๋น์ฉ ์ถ์
tracker.track_request(
model="gpt-3.5-turbo",
input_tokens=response.usage.prompt_tokens,
output_tokens=response.usage.completion_tokens
)
# ๋น์ฉ ๊ฒฝ๊ณ
if tracker.should_switch_model(threshold=10.0):
logger.warning("โ ๏ธ ์ ๋น์ฉ์ด $10๋ฅผ ์ด๊ณผํ์ต๋๋ค!")
return response.choices[0].message.content
๐ ๊ณ ๊ธ ๊ธฐ๋ฅ ์ถ๊ฐํ๊ธฐ
๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ๋ค ๋ง๋ค์์ผ๋, ์ด์ ๋ ๋ฉ์ง ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํด๋ณด์! โจ
1. ๋ฆฌํฌํธ ๋น๊ต ๋ถ์
๊ฐ์ ์ข ๋ชฉ์ ๋ํ ์ฌ๋ฌ ์ฆ๊ถ์ฌ์ ๋ฆฌํฌํธ๋ฅผ ๋น๊ตํ๋ฉด ๋ ๊ฐ๊ด์ ์ธ ํ๋จ์ด ๊ฐ๋ฅํด!
def compare_reports(reports: List[Dict]) -> Dict:
"""์ฌ๋ฌ ๋ฆฌํฌํธ ๋น๊ต ๋ถ์"""
comparison = {
'stock': reports[0]['stock_name'],
'report_count': len(reports),
'opinions': {},
'target_prices': [],
'consensus': {}
}
# ํฌ์์๊ฒฌ ์ง๊ณ
for report in reports:
opinion = report.get('opinion', 'N/A')
comparison['opinions'][opinion] = comparison['opinions'].get(opinion, 0) + 1
target_price = report.get('target_price')
if target_price:
comparison['target_prices'].append({
'firm': report['firm'],
'price': target_price
})
# ์ปจ์ผ์์ค ๊ณ์ฐ
if comparison['target_prices']:
prices = [tp['price'] for tp in comparison['target_prices']]
comparison['consensus'] = {
'avg_target_price': sum(prices) / len(prices),
'max_target_price': max(prices),
'min_target_price': min(prices),
'std_deviation': statistics.stdev(prices) if len(prices) > 1 else 0
}
# ๊ฐ์ฅ ๋ง์ ์๊ฒฌ
if comparison['opinions']:
comparison['majority_opinion'] = max(
comparison['opinions'].items(),
key=lambda x: x[1]
)[0]
return comparison
# ์ฌ์ฉ ์์
samsung_reports = [
{'stock_name': '์ผ์ฑ์ ์', 'firm': 'ํ๊ตญํฌ์์ฆ๊ถ', 'opinion': '๋งค์', 'target_price': 85000},
{'stock_name': '์ผ์ฑ์ ์', 'firm': 'NHํฌ์์ฆ๊ถ', 'opinion': '๋งค์', 'target_price': 87000},
{'stock_name': '์ผ์ฑ์ ์', 'firm': '๋ฏธ๋์์
์ฆ๊ถ', 'opinion': '์ค๋ฆฝ', 'target_price': 80000},
]
comparison = compare_reports(samsung_reports)
print(f"์ปจ์ผ์์ค ๋ชฉํ์ฃผ๊ฐ: {comparison['consensus']['avg_target_price']:,.0f}์")
print(f"๋ค์ ์๊ฒฌ: {comparison['majority_opinion']}")
2. ์๋ฆผ ์์คํ
์ค์ํ ๋ณํ๊ฐ ์์ ๋ ์ฆ์ ์๋ ค์ฃผ๋ ์์คํ !
import requests
class AlertSystem:
"""์๋ฆผ ์์คํ
"""
def __init__(self, slack_webhook: str = None, telegram_token: str = None):
self.slack_webhook = slack_webhook
self.telegram_token = telegram_token
def should_alert(self, analysis: Dict) -> bool:
"""์๋ฆผ์ด ํ์ํ์ง ํ๋จ"""
# ์๋ฆผ ์กฐ๊ฑด๋ค
conditions = [
# ๋ชฉํ์ฃผ๊ฐ ๋ํญ ์ํฅ (10% ์ด์)
analysis.get('target_price_change', 0) > 10,
# ํฌ์์๊ฒฌ ๋ณ๊ฒฝ
analysis.get('opinion_changed', False),
# ์์น์ฌ๋ ฅ 20% ์ด์
analysis.get('upside_potential', 0) > 20,
# ์ค์ ์ํ๋ผ์ด์ฆ
'surprise' in analysis.get('keywords', [])
]
return any(conditions)
def send_slack(self, message: str):
"""์ฌ๋ ์๋ฆผ"""
if not self.slack_webhook:
return
payload = {
'text': message,
'username': 'Report Analyzer Bot',
'icon_emoji': ':chart_with_upwards_trend:'
}
try:
response = requests.post(self.slack_webhook, json=payload)
if response.status_code == 200:
logger.info("โ
์ฌ๋ ์๋ฆผ ์ ์ก ์๋ฃ")
except Exception as e:
logger.error(f"์ฌ๋ ์๋ฆผ ์คํจ: {e}")
def send_telegram(self, message: str, chat_id: str):
"""ํ
๋ ๊ทธ๋จ ์๋ฆผ"""
if not self.telegram_token:
return
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
payload = {
'chat_id': chat_id,
'text': message,
'parse_mode': 'Markdown'
}
try:
response = requests.post(url, json=payload)
if response.status_code == 200:
logger.info("โ
ํ
๋ ๊ทธ๋จ ์๋ฆผ ์ ์ก ์๋ฃ")
except Exception as e:
logger.error(f"ํ
๋ ๊ทธ๋จ ์๋ฆผ ์คํจ: {e}")
def format_alert_message(self, analysis: Dict) -> str:
"""์๋ฆผ ๋ฉ์์ง ํฌ๋งท"""
message = f"""
๐จ *์ค์ ๋ฆฌํฌํธ ์๋ฆผ*
๐ ์ข
๋ชฉ: {analysis.get('stock_name', 'N/A')}
๐ข ์ฆ๊ถ์ฌ: {analysis.get('firm', 'N/A')}
๐ฐ ๋ชฉํ์ฃผ๊ฐ: {analysis.get('target_price', 'N/A'):,}์
๐ ์์น์ฌ๋ ฅ: {analysis.get('upside_potential', 'N/A')}%
โญ ํฌ์์๊ฒฌ: {analysis.get('opinion', 'N/A')}
๐ ํต์ฌ ์์ฝ:
{analysis.get('summary', 'N/A')[:200]}...
"""
return message.strip()
# ์ฌ์ฉ ์์
alert_system = AlertSystem(
slack_webhook="your-slack-webhook-url",
telegram_token="your-telegram-bot-token"
)
def process_with_alert(report_path: str):
"""์๋ฆผ ํฌํจ ์ฒ๋ฆฌ"""
analysis = process_report(report_path)
if alert_system.should_alert(analysis):
message = alert_system.format_alert_message(analysis)
alert_system.send_slack(message)
alert_system.send_telegram(message, chat_id="your-chat-id")
๐ฑ ๋ชจ๋ฐ์ผ ์ฑ ์ฐ๋
์ด์ ๋ชจ๋ฐ์ผ์์๋ ํธํ๊ฒ ํ์ธํ ์ ์๊ฒ ๋ง๋ค์ด๋ณด์! ๐ฒ
REST API ๋ง๋ค๊ธฐ
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # ๋ชจ๋ฐ์ผ ์ฑ์์ ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก
# ์ ์ญ ๋ถ์๊ธฐ
analyzer = RobustReportAnalyzer()
@app.route('/api/reports', methods=['GET'])
def get_reports():
"""๋ฆฌํฌํธ ๋ชฉ๋ก ์กฐํ"""
# ์ค์ ๋ก๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํ
reports = [
{
'id': 1,
'stock': '์ผ์ฑ์ ์',
'firm': 'ํ๊ตญํฌ์์ฆ๊ถ',
'date': '2024-01-15',
'opinion': '๋งค์',
'target_price': 85000
},
# ... ๋ ๋ง์ ๋ฆฌํฌํธ
]
return jsonify({
'success': True,
'data': reports,
'count': len(reports)
})
@app.route('/api/reports/<int:report_id>', methods=['GET'])
def get_report_detail(report_id):
"""๋ฆฌํฌํธ ์์ธ ์กฐํ"""
# ์ค์ ๋ก๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํ
report = {
'id': report_id,
'stock': '์ผ์ฑ์ ์',
'firm': 'ํ๊ตญํฌ์์ฆ๊ถ',
'date': '2024-01-15',
'opinion': '๋งค์',
'target_price': 85000,
'summary': 'AI๊ฐ ์์ฑํ ์์ฝ ๋ด์ฉ...',
'key_points': [
'๋ฉ๋ชจ๋ฆฌ ๋ฐ๋์ฒด ๊ฐ๊ฒฉ ์์น',
'1๋ถ๊ธฐ ์ค์ ๊ฐ์ ์ ๋ง',
'HBM3 ์์ ์ฆ๊ฐ'
]
}
return jsonify({
'success': True,
'data': report
})
@app.route('/api/analyze', methods=['POST'])
def analyze_report():
"""์ ๋ฆฌํฌํธ ๋ถ์"""
# ํ์ผ ์
๋ก๋ ๋ฐ๊ธฐ
if 'file' not in request.files:
return jsonify({
'success': False,
'error': 'ํ์ผ์ด ์์ต๋๋ค'
}), 400
file = request.files['file']
# ์์ ์ ์ฅ
temp_path = f"/tmp/{file.filename}"
file.save(temp_path)
# ๋ถ์ ์คํ
try:
result = analyzer.safe_process(temp_path)
return jsonify({
'success': True,
'data': result
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
@app.route('/api/search', methods=['GET'])
def search_reports():
"""๋ฆฌํฌํธ ๊ฒ์"""
query = request.args.get('q', '')
stock = request.args.get('stock', '')
# ๊ฒ์ ๋ก์ง (์ค์ ๋ก๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฟผ๋ฆฌ)
results = []
return jsonify({
'success': True,
'data': results,
'query': query
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
๐ ํ์ต ๋ฆฌ์์ค ๋ฐ ๋ค์ ๋จ๊ณ
์ฌ๊ธฐ๊น์ง ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐ ์ด์ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ๊ฒ๋ค์ ์ถ์ฒํด:
๐ ์ถ์ฒ ํ์ต ์๋ฃ:
์์ฐ์ด ์ฒ๋ฆฌ (NLP):
โข Hugging Face Transformers ๊ณต์ ๋ฌธ์
โข "Natural Language Processing with Transformers" ์ฑ
โข Stanford CS224N ๊ฐ์
๋จธ์ ๋ฌ๋/๋ฅ๋ฌ๋:
โข Andrew Ng์ Machine Learning ๊ฐ์
โข Fast.ai ์ค์ ๋ฅ๋ฌ๋ ๊ฐ์
โข "Hands-On Machine Learning" ์ฑ
๊ธ์ต ๋ฐ์ดํฐ ๋ถ์:
โข FinanceDataReader ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข Quantitative Finance ๊ด๋ จ ๋
ผ๋ฌธ๋ค
โข ์ฆ๊ถ์ฌ API ๋ฌธ์ (ํ๊ตญํฌ์์ฆ๊ถ, ํค์์ฆ๊ถ ๋ฑ)
์น ๊ฐ๋ฐ:
โข Flask/FastAPI ๊ณต์ ๋ฌธ์
โข React Native (๋ชจ๋ฐ์ผ ์ฑ)
โข Docker & Kubernetes (๋ฐฐํฌ)
๐ ๋ค์ ๋จ๊ณ ํ๋ก์ ํธ ์์ด๋์ด
1. ์ค์๊ฐ ๋ด์ค ๋ถ์ ์์คํ
โข ๋ค์ด๋ฒ ๋ด์ค, ๊ตฌ๊ธ ๋ด์ค ํฌ๋กค๋ง
โข ์ค์๊ฐ ๊ฐ์ฑ ๋ถ์
โข ์ฃผ๊ฐ ์ํฅ๋ ์์ธก
2. ํฌํธํด๋ฆฌ์ค ์ถ์ฒ ์์คํ
โข ๋ฆฌํฌํธ ๋ถ์ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ
โข ๋ฆฌ์คํฌ ๊ด๋ฆฌ ์๊ณ ๋ฆฌ์ฆ
โข ๋ฐฑํ
์คํ
๊ธฐ๋ฅ
3. ์์
ํธ๋ ์ด๋ฉ ํ๋ซํผ
โข ๋ถ์ ๊ฒฐ๊ณผ ๊ณต์
โข ์ปค๋ฎค๋ํฐ ๊ธฐ๋ฅ
โข ์ค์๊ฐ ์ฑํ
4. AI ํฌ์ ์ด์์คํดํธ ์ฑ๋ด
โข ์นด์นด์คํก/ํ
๋ ๊ทธ๋จ ๋ด
โข ์์ฑ ์ธ์ ๊ธฐ๋ฅ
โข ๊ฐ์ธํ๋ ์ถ์ฒ
๐ก ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์์ฐ, ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด! ๐
์ฐ๋ฆฌ๊ฐ ํจ๊ป ์์๋ณธ ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด๋ฉด:
โ
AI๋ก ํฌ์ ๋ฆฌํฌํธ๋ฅผ ์๋ ๋ถ์ํ๋ ์ด์ ์ ๋ฐฉ๋ฒ
โ
PDF ํ์ฑ๋ถํฐ ์์ฐ์ด ์ฒ๋ฆฌ๊น์ง ์ ์ฒด ํ์ดํ๋ผ์ธ
โ
GPT API์ ์คํ์์ค ๋ชจ๋ธ ํ์ฉ๋ฒ
โ
์๋ํ ์์คํ
๊ตฌ์ถ ๋ฐ ์ค์ผ์ค๋ง
โ
๋์๋ณด๋ ์ ์๊ณผ ์๊ฐํ
โ
์ฑ๋ฅ ์ต์ ํ์ ๋น์ฉ ๊ด๋ฆฌ
โ
์๋ฌ ์ฒ๋ฆฌ์ ๋ณด์
โ
๋ชจ๋ฐ์ผ ์ฑ ์ฐ๋๊น์ง!
์ด ๋ชจ๋ ๊ฑธ ์ฒ์๋ถํฐ ์๋ฒฝํ๊ฒ ๋ง๋ค ํ์๋ ์์ด. ์์ ๊ฒ๋ถํฐ ์์ํด์ ์ ์ง์ ์ผ๋ก ๊ฐ์ ํด๋๊ฐ๋ฉด ๋ผ! ๐ช
ํน์ ํผ์ ํ๊ธฐ ์ด๋ ต๋ค๋ฉด ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ด๋ฐ ํ๋ก์ ํธ๋ฅผ ๋์์ฃผ๊ณ ์๊ฑฐ๋ ! ๐ค
ํฌ์๋ ํญ์ ์ ์คํ๊ฒ, ๊ทธ๋ฆฌ๊ณ AI๋ ๋๊ตฌ์ผ ๋ฟ์ด๋ผ๋ ๊ฑธ ์์ง ๋ง! ์ต์ข
ํ๋จ์ ํญ์ ์ฌ๋์ด ํด์ผ ํด. ๐
์, ์ด์ ๋น์ ๋ AI ํฌ์ ๋ฆฌํฌํธ ๋ถ์ ์ ๋ฌธ๊ฐ๊ฐ ๋์์ด! ๐โจ
๊ถ๊ธํ ์ ์ด ์๊ฑฐ๋ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค๊ฐ ๋งํ๋ ๋ถ๋ถ์ด ์์ผ๋ฉด ์ธ์ ๋ ์ง ์ปค๋ฎค๋ํฐ์ ๋ฌผ์ด๋ณด๊ฑฐ๋, ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์๋ด. ํจ๊ป ์ฑ์ฅํ๋ ๊ฒ ์ค์ํ๋๊น! ๐ฑ
ํ์ด์ ๋น์ด! ํ์ดํ
! ๐๐ฐ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.