DEVELOPMENT_GUIDE.mdโข12.6 kB
# ๐ DART API ๊ฐ๋ฐ ๊ฐ์ด๋
์ด ๋ฌธ์๋ DART API์ ๊ฐ ๊ทธ๋ฃน๋ณ ๊ธฐ๋ฅ๊ณผ ๊ฐ๋ฐ ๋ฐฉ๋ฒ์ ์๋ดํฉ๋๋ค.
## ๐ DART API ๊ทธ๋ฃน ๊ฐ์
### DS001: ๊ณต์์ ๋ณด ์กฐํ
- **๊ธฐ๋ฅ**: ๊ธฐ์
์ ๊ณต์์ ๋ณด ๋ชฉ๋ก ๋ฐ ์์ธ ์กฐํ
- **ํ์ฌ ๊ตฌํ**: โ
`get_public_disclosure_tool`
- **๊ฐ๋ฐ ๊ฐ์ด๋**: https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS001
### DS002: ์ฌ๋ฌด์ ๋ณด ์กฐํ
- **๊ธฐ๋ฅ**: ์ฌ๋ฌด์ ํ, ์์ต๊ณ์ฐ์, ํ๊ธํ๋ฆํ ๋ฑ ์ฌ๋ฌด์ ๋ณด ์กฐํ
- **ํ์ฌ ๊ตฌํ**: โ
`get_financial_statement_tool`, `analyze_financial_trend_tool`
- **๊ฐ๋ฐ ๊ฐ์ด๋**: https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS002
### DS003: ๊ธฐ์
๊ฐํฉ ์ ๋ณด
- **๊ธฐ๋ฅ**: ๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด, ์ฃผ์์ฌํญ, ์์์ ๋ณด ๋ฑ
- **ํ์ฌ ๊ตฌํ**: โ
๋๋ถ๋ถ ๊ตฌํ ์๋ฃ
- โ
๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด ์กฐํ (`get_company_overview_tool`)
- โ
์์์ ๋ณด ์กฐํ (`get_executives_tool`)
- โ ๏ธ ์ฃผ์์ฌํญ ์กฐํ (`/majorIssues.json`) - ๋ฏธ๊ตฌํ (๋ ์ค์)
- โ ๏ธ ์ฌ์
์ ๋ด์ฉ ์กฐํ (`/bizrNo.json`) - ๋ฏธ๊ตฌํ (๋ ์ค์)
- **๊ฐ๋ฐ ๊ฐ์ด๋**: https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS003
### DS004: ๊ณต์์๋ฌธ ๋ค์ด๋ก๋
- **๊ธฐ๋ฅ**: ๊ณต์๋ณด๊ณ ์ ์๋ฌธ(XML, PDF) ๋ค์ด๋ก๋
- **ํ์ฌ ๊ตฌํ**: โ
๊ตฌํ ์๋ฃ (`download_disclosure_document_tool`)
- **๊ฐ๋ฐ ๊ฐ์ด๋**: https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS004
### DS005: ์ฃผ์์ฌํญ๋ณด๊ณ ์
- **๊ธฐ๋ฅ**: ์ฃผ์์ฌํญ๋ณด๊ณ ์ ์กฐํ ๋ฐ ๋ถ์
- **ํ์ฌ ๊ตฌํ**: โ
๊ตฌํ ์๋ฃ (`get_major_report_tool`)
- **๊ฐ๋ฐ ๊ฐ์ด๋**: https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS005
### DS006: ๊ธฐํ ์ ๋ณด
- **๊ธฐ๋ฅ**: ์ง๋ถ๋ณด๊ณ ์, ์ฆ๊ถ์ ๊ณ ์ ๋ฑ ๊ธฐํ ๊ณต์์ ๋ณด
- **ํ์ฌ ๊ตฌํ**: โ ๏ธ ๋ถ๋ถ ๊ตฌํ
- โ
์ง๋ถ๋ณด๊ณ ์ ์กฐํ (`get_shareholders_tool`)
- โ ๏ธ ์ฆ๊ถ์ ๊ณ ์ ์กฐํ (`/securitiesReport.json`) - ๋ฏธ๊ตฌํ (๋ ์ค์)
- **๊ฐ๋ฐ ๊ฐ์ด๋**: https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS006
---
## ๐ ๏ธ ์ถ๊ฐ ๊ฐ๋ฐ ๊ฐ์ด๋
### 1. DS003: ๊ธฐ์
๊ฐํฉ ์ ๋ณด ์ถ๊ฐ ๊ฐ๋ฐ
#### 1.1 ๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด ์กฐํ
```python
def get_company_overview(corp_code: str, arguments: Optional[dict] = None) -> Dict:
"""
๊ธฐ์
์ ๊ธฐ๋ณธ์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค.
API: /company.json
"""
api_url = f"{DART_API_URL}/company.json"
params = {
"crtfc_key": api_key,
"corp_code": corp_code,
}
# ๊ตฌํ...
```
#### 1.2 ์์์ ๋ณด ์กฐํ
```python
def get_executives(corp_code: str, arguments: Optional[dict] = None) -> Dict:
"""
๊ธฐ์
์ ์์์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค.
API: /empSttus.json
"""
api_url = f"{DART_API_URL}/empSttus.json"
# ๊ตฌํ...
```
#### 1.3 ์ฃผ์์ฌํญ ์กฐํ
```python
def get_major_issues(corp_code: str, arguments: Optional[dict] = None) -> Dict:
"""
๊ธฐ์
์ ์ฃผ์์ฌํญ์ ์กฐํํฉ๋๋ค.
API: /majorIssues.json
"""
api_url = f"{DART_API_URL}/majorIssues.json"
# ๊ตฌํ...
```
### 2. DS004: ๊ณต์์๋ฌธ ๋ค์ด๋ก๋ ์ถ๊ฐ ๊ฐ๋ฐ
#### 2.1 ๊ณต์์๋ฌธ ๋ค์ด๋ก๋
```python
def download_disclosure_document(rcept_no: str, arguments: Optional[dict] = None) -> Dict:
"""
๊ณต์์๋ฌธ์ ๋ค์ด๋ก๋ํฉ๋๋ค.
API: /document.xml ๋๋ /document.pdf
"""
api_url = f"{DART_API_URL}/document.xml"
params = {
"crtfc_key": api_key,
"rcept_no": rcept_no,
}
# ๊ตฌํ...
```
#### 2.2 XML ํ์ฑ ๋ฐ ๋ฐ์ดํฐ ์ถ์ถ
```python
def parse_disclosure_xml(xml_content: bytes) -> Dict:
"""
๊ณต์์๋ฌธ XML์ ํ์ฑํ์ฌ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ๋ก ๋ณํํฉ๋๋ค.
"""
import xml.etree.ElementTree as ET
root = ET.fromstring(xml_content)
# ํ์ฑ ๋ก์ง...
```
### 3. DS005: ์ฃผ์์ฌํญ๋ณด๊ณ ์ ์ถ๊ฐ ๊ฐ๋ฐ
#### 3.1 ์ฃผ์์ฌํญ๋ณด๊ณ ์ ์กฐํ
```python
def get_major_report(corp_code: str, bgn_de: str = None, end_de: str = None,
arguments: Optional[dict] = None) -> Dict:
"""
์ฃผ์์ฌํญ๋ณด๊ณ ์๋ฅผ ์กฐํํฉ๋๋ค.
API: /majorReport.json
"""
api_url = f"{DART_API_URL}/majorReport.json"
params = {
"crtfc_key": api_key,
"corp_code": corp_code,
"bgn_de": bgn_de or (datetime.now() - timedelta(days=30)).strftime("%Y%m%d"),
"end_de": end_de or datetime.now().strftime("%Y%m%d"),
}
# ๊ตฌํ...
```
### 4. DS006: ๊ธฐํ ์ ๋ณด ์ถ๊ฐ ๊ฐ๋ฐ
#### 4.1 ์ง๋ถ๋ณด๊ณ ์ ์กฐํ
```python
def get_shareholders_report(corp_code: str, arguments: Optional[dict] = None) -> Dict:
"""
์ง๋ถ๋ณด๊ณ ์๋ฅผ ์กฐํํฉ๋๋ค.
API: /shareholders.json
"""
api_url = f"{DART_API_URL}/shareholders.json"
# ๊ตฌํ...
```
#### 4.2 ์ฆ๊ถ์ ๊ณ ์ ์กฐํ
```python
def get_securities_report(corp_code: str, arguments: Optional[dict] = None) -> Dict:
"""
์ฆ๊ถ์ ๊ณ ์๋ฅผ ์กฐํํฉ๋๋ค.
API: /securitiesReport.json
"""
api_url = f"{DART_API_URL}/securitiesReport.json"
# ๊ตฌํ...
```
---
## ๐ง ๊ตฌํ ์์
### ์์ 1: ๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด ์กฐํ ๋๊ตฌ ์ถ๊ฐ
`src/tools.py`์ ์ถ๊ฐ:
```python
def get_company_overview(corp_code: Optional[str] = None, company_name: Optional[str] = None,
arguments: Optional[dict] = None) -> Dict:
"""
๊ธฐ์
์ ๊ธฐ๋ณธ์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค.
"""
# corp_code๊ฐ ์์ผ๋ฉด company_name์ผ๋ก ๊ฒ์
if not corp_code and company_name:
search_result = search_company(company_name, arguments)
if "error" in search_result:
return {"error": f"๊ธฐ์
๊ฒ์ ์คํจ: {search_result['error']}"}
companies = search_result.get("companies", [])
if not companies:
return {"error": f"'{company_name}'์ ํด๋นํ๋ ๊ธฐ์
์ ์ฐพ์ ์ ์์ต๋๋ค."}
corp_code = companies[0].get("corp_code")
if not corp_code:
return {"error": "corp_code ๋๋ company_name ์ค ํ๋๋ ํ์์
๋๋ค."}
# corp_code ์ ๊ทํ
corp_code = str(corp_code).strip()
if corp_code.isdigit():
corp_code = corp_code.zfill(8)
credentials = get_credentials(arguments)
api_key = credentials["DART_API_KEY"]
if not api_key:
return {"error": "API ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค."}
api_url = f"{DART_API_URL}/company.json"
params = {
"crtfc_key": api_key,
"corp_code": corp_code,
}
try:
response = requests.get(api_url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
if data.get("status") == "000":
return {
"corp_code": corp_code,
"company_info": data
}
else:
return {"error": f"DART API ์ค๋ฅ: {data.get('message', '์ ์ ์๋ ์ค๋ฅ')}"}
except Exception as e:
logger.exception("Company overview error: %s", str(e))
return {"error": f"๊ธฐ์
์ ๋ณด ์กฐํ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}"}
```
`src/main.py`์ MCP ๋๊ตฌ ์ถ๊ฐ:
```python
@mcp.tool()
async def get_company_overview_tool(
corp_code: Optional[str] = None,
company_name: Optional[str] = None
):
"""
๊ธฐ์
์ ๊ธฐ๋ณธ์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค.
"""
req = CompanyOverviewRequest(
corp_code=corp_code,
company_name=company_name
)
return await get_company_overview_impl(req)
```
### ์์ 2: ๊ณต์์๋ฌธ ๋ค์ด๋ก๋ ๋๊ตฌ ์ถ๊ฐ
```python
def download_disclosure_document(rcept_no: str, file_format: str = "xml",
arguments: Optional[dict] = None) -> Dict:
"""
๊ณต์์๋ฌธ์ ๋ค์ด๋ก๋ํฉ๋๋ค.
Args:
rcept_no: ์ ์๋ฒํธ
file_format: ํ์ผ ํ์ ("xml" ๋๋ "pdf")
"""
credentials = get_credentials(arguments)
api_key = credentials["DART_API_KEY"]
if not api_key:
return {"error": "API ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค."}
api_url = f"{DART_API_URL}/document.{file_format}"
params = {
"crtfc_key": api_key,
"rcept_no": rcept_no,
}
try:
response = requests.get(api_url, params=params, timeout=60)
response.raise_for_status()
if file_format == "xml":
# XML ํ์ฑ
import xml.etree.ElementTree as ET
root = ET.fromstring(response.content)
# ํ์ฑ ๋ก์ง...
return {
"rcept_no": rcept_no,
"format": file_format,
"content": response.text,
"parsed": True
}
else:
# PDF๋ ๋ฐ์ด๋๋ฆฌ๋ก ๋ฐํ
import base64
return {
"rcept_no": rcept_no,
"format": file_format,
"content_base64": base64.b64encode(response.content).decode('utf-8'),
"size": len(response.content)
}
except Exception as e:
logger.exception("Document download error: %s", str(e))
return {"error": f"๊ณต์์๋ฌธ ๋ค์ด๋ก๋ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}"}
```
---
## ๐ ๊ฐ๋ฐ ์ฒดํฌ๋ฆฌ์คํธ
### DS003 ๊ตฌํ
- [x] ๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด ์กฐํ (`/company.json`) โ
- [x] ์์์ ๋ณด ์กฐํ (`/empSttus.json`) โ
- [ ] ์ฃผ์์ฌํญ ์กฐํ (`/majorIssues.json`) - ์ ํ์ฌํญ
- [ ] ์ฌ์
์ ๋ด์ฉ ์กฐํ (`/bizrNo.json`) - ์ ํ์ฌํญ
### DS004 ๊ตฌํ
- [x] ๊ณต์์๋ฌธ XML ๋ค์ด๋ก๋ (`/document.xml`) โ
- [x] ๊ณต์์๋ฌธ PDF ๋ค์ด๋ก๋ (`/document.pdf`) โ
- [x] XML ํ์ฑ ๋ฐ ๋ฐ์ดํฐ ์ถ์ถ โ
- [ ] PDF ํ
์คํธ ์ถ์ถ (์ ํ์ฌํญ) - ํ์ฌ๋ base64 ์ธ์ฝ๋ฉ์ผ๋ก ๋ฐํ
### DS005 ๊ตฌํ
- [x] ์ฃผ์์ฌํญ๋ณด๊ณ ์ ์กฐํ (`/majorReport.json`) โ
- [x] ์ฃผ์์ฌํญ๋ณด๊ณ ์ ํํฐ๋ง (๊ธฐ๊ฐ, ์ ํ๋ณ) โ
### DS006 ๊ตฌํ
- [x] ์ง๋ถ๋ณด๊ณ ์ ์กฐํ (`/shareholders.json`) โ
- [ ] ์ฆ๊ถ์ ๊ณ ์ ์กฐํ (`/securitiesReport.json`) - ์ ํ์ฌํญ
- [ ] ๊ธฐํ ๊ณต์์ ๋ณด ์กฐํ - ์ ํ์ฌํญ
---
## ๐ API ์๋ํฌ์ธํธ ์ฐธ๊ณ
### ๊ณตํต ํ๋ผ๋ฏธํฐ
- `crtfc_key`: ์ธ์ฆํค (ํ์)
- `corp_code`: ๊ธฐ์
๊ณ ์ ๋ฒํธ (8์๋ฆฌ, ํ์)
- `bgn_de`: ์์์ผ (YYYYMMDD ํ์)
- `end_de`: ์ข
๋ฃ์ผ (YYYYMMDD ํ์)
### ์๋ต ํ์
```json
{
"status": "000", // "000": ์ฑ๊ณต, "013": ๋ฐ์ดํฐ ์์, ๊ธฐํ: ์ค๋ฅ
"message": "์ ์",
"list": [...] // ๋ฐ์ดํฐ ๋ฐฐ์ด
}
```
### ์ค๋ฅ ์ฝ๋
- `000`: ์ ์
- `010`: ๋ฑ๋ก๋์ง ์์ ํค
- `011`: ์ฌ์ฉํ ์ ์๋ ํค
- `012`: ์ ๊ทผํ ์ ์๋ IP
- `013`: ์กฐํ๋ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค
- `020`: ์์ฒญ ์ ํ ์ด๊ณผ
---
## ๐ ๊ฐ๋ฐ ํ
### 1. ์บ์ฑ ์ ๋ต
```python
# ๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด๋ ์์ฃผ ๋ณํ์ง ์์ผ๋ฏ๋ก ๊ธด TTL ์ฌ์ฉ
company_overview_cache = TTLCache(maxsize=100, ttl=86400 * 7) # 7์ผ
# ๊ณต์์ ๋ณด๋ ์์ฃผ ์
๋ฐ์ดํธ๋๋ฏ๋ก ์งง์ TTL ์ฌ์ฉ
disclosure_cache = TTLCache(maxsize=100, ttl=3600) # 1์๊ฐ
```
### 2. ์๋ฌ ํธ๋ค๋ง
```python
try:
response = requests.get(api_url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
if data.get("status") == "000":
return {"data": data.get("list", [])}
elif data.get("status") == "013":
return {"error": "์กฐํ๋ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค."}
else:
return {"error": f"DART API ์ค๋ฅ: {data.get('message')}"}
except requests.exceptions.Timeout:
return {"error": "API ์์ฒญ ์๊ฐ ์ด๊ณผ"}
except requests.exceptions.RequestException as e:
return {"error": f"API ์์ฒญ ์คํจ: {str(e)}"}
```
### 3. ๋ก๊น
```python
logger.debug("API request | url=%s params=%s", api_url,
{k: v if k != "crtfc_key" else v[:6] + "***" for k, v in params.items()})
logger.info("API response | status=%s items=%d", data.get("status"), len(data.get("list", [])))
```
---
## ๐ ์ฐธ๊ณ ์๋ฃ
- [DART API ๊ณต์ ๋ฌธ์](https://opendart.fss.or.kr/guide/main.do)
- [DART API ๊ทธ๋ฃน๋ณ ๊ฐ์ด๋](https://opendart.fss.or.kr/guide/main.do?apiGrpCd=DS001)
- [Python requests ๋ฌธ์](https://requests.readthedocs.io/)
- [FastMCP ๋ฌธ์](https://github.com/jlowin/fastmcp)
---
## ๐ก ๋ค์ ๋จ๊ณ
1. **DS003 ๊ตฌํ**: ๊ธฐ์
๊ธฐ๋ณธ์ ๋ณด, ์์์ ๋ณด ์กฐํ ๊ธฐ๋ฅ ์ถ๊ฐ
2. **DS004 ๊ตฌํ**: ๊ณต์์๋ฌธ ๋ค์ด๋ก๋ ๋ฐ ํ์ฑ ๊ธฐ๋ฅ ์ถ๊ฐ
3. **DS005 ๊ตฌํ**: ์ฃผ์์ฌํญ๋ณด๊ณ ์ ์กฐํ ๊ธฐ๋ฅ ์ถ๊ฐ
4. **DS006 ๊ตฌํ**: ์ง๋ถ๋ณด๊ณ ์, ์ฆ๊ถ์ ๊ณ ์ ์กฐํ ๊ธฐ๋ฅ ์ถ๊ฐ
5. **ํ
์คํธ**: ๊ฐ ๊ธฐ๋ฅ์ ๋ํ ๋จ์ ํ
์คํธ ๋ฐ ํตํฉ ํ
์คํธ ์์ฑ
6. **๋ฌธ์ํ**: README ๋ฐ API ๋ฌธ์ ์
๋ฐ์ดํธ
---
**Happy Coding! ๐**