├── .python-version
├── .DS_Store
├── .gitignore
├── pyproject.toml
├── README.md
├── uv.lock
└── dart.py
/.python-version:
--------------------------------------------------------------------------------
1 | 3.10
2 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wikibook/dart-mcp/main/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/
6 | wheels/
7 | *.egg-info
8 |
9 | # Virtual environments
10 | .venv
11 | .env
12 | local.env
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "dart-mcp"
3 | version = "0.1.0"
4 | description = "Add your description here"
5 | readme = "README.md"
6 | requires-python = ">=3.10"
7 | dependencies = [
8 | "httpx>=0.28.1",
9 | "mcp[cli]>=1.6.0",
10 | "python-dotenv>=1.0.0",
11 | ]
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DART-MCP: 재무 분석을 위한 Claude 확장 프로그램
2 |
3 | DART API를 활용한 재무 분석 MCP(Model-assisted Capability Package)입니다. Claude를 이용하여 상장 기업의 재무 데이터를 쉽게 분석하고 시각화할 수 있습니다.
4 |
5 | ## 가능한 것 / 불가능한 것
6 |
7 | ### 가능한 것 (O)
8 | - 주요 재무 분석
9 | - 상세 재무 분석
10 | - 기업의 사업부별 매출
11 | - 클로드를 이용한 시각화
12 | - 재무지표를 활용한 벨류에이션 (DCF 등)
13 |
14 | ### 불가능한 것 (X)
15 | - 주가 및 시가총액 제공
16 | - 해외기업 분석
17 | - 클로드 무료 사용량 이상의 사용
18 | - 한 채팅창에서 다량 사용 (잘 안되면 채팅창 새로 만들어서 쓰기)
19 | - 100% 정확한 정보
20 |
21 | **제공하는 투자 정보는 실제와 다를 수 있고 투자 책임은 투자한 본인에게 있습니다.**
22 |
23 | ## 사용 예시
24 |
25 | ### 재무 데이터 분석 및 시각화
26 | ```
27 | 파마리서치의 2023, 2024년 매출액, 영업이익 추이 분기별로 그래프로 보여줘. 그리고 매출비중이 어떻게 되는지 알려줘. 영업이익이나 매출액 변동 이유도 분석해줘.
28 | ```
29 |
30 | ### 기업 비교 분석
31 | ```
32 | 카카오와 네이버 2024년 수익성지표를 비교해서 분기별로 보여주고, 각 기업들은 어떤 사업부가 성장을 이끌지 알려줘.
33 | ```
34 |
35 | ### 재무 위험 평가
36 | ```
37 | 한국전력의 최근 부채상황을 조사하고, 상세하게 어떤 부분이 문제인지 분석해줘.
38 | ```
39 |
40 | ## 사전 준비
41 |
42 | ### DART API 키 발급
43 | 1. [DART 오픈API](https://opendart.fss.or.kr) 웹사이트에 접속
44 | 2. 회원가입 및 로그인
45 | 3. [인증키 신청/관리] - [오픈API 이용 신청] 메뉴 클릭
46 | 4. 이용정보 입력 후 신청
47 | 5. [인증키 신청/관리] - [오픈API 이용현황] 메뉴에서 발급된 인증키 확인
48 |
49 | ### Claude 데스크톱 앱 설치
50 | 1. [Claude 데스크톱 앱](https://claude.ai/desktop) 다운로드
51 | 2. 계정 가입 및 로그인
52 |
53 | ## 설치 방법
54 |
55 | ### 1. GitHub에서 프로젝트 다운로드
56 | GitHub 페이지에서 zip 파일을 다운로드합니다.
57 | https://github.com/2geonhyup/dart-mcp
58 |
59 | ### 2. ZIP 파일 압축 해제 및 폴더 위치 확인
60 | 1) 다운로드한 ZIP 파일의 압축을 해제합니다.
61 | 2) 압축 해제 폴더가 **Downloads**에 있는지 확인합니다. **다른 위치에 있다면 Downloads 위치로 옮겨주세요.**
62 |
63 | ### 3. 폴더 이름 변경
64 | 압축 해제한 폴더 dart-mcp-main 이름을 **dart-mcp로 반드시 바꿔주세요.** (처음부터 dart-mcp라면 바꾸지 마세요)
65 |
66 | ### 4. Claude 앱 접속 및 설정 접근
67 | 1) 설치한 Claude 데스크톱 앱을 실행합니다.
68 | 2) 맥 사용자: **Claude > 설정 > 개발자 > 설정 편집** 클릭
69 | 윈도우 사용자: **설정 > 개발자 > 설정 편집** 클릭
70 |
71 | ### 5. 설정 파일 열기
72 | **상단 claude_desktop_config 파일**을 텍스트 편집기로 엽니다.
73 |
74 | ### 6. 설정 코드 입력
75 | 1. 먼저 알맞은 키와 이름을 입력하세요
76 | - DART API 키: 발급받은 API 키 입력
77 | - 컴퓨터 이름: 컴퓨터 계정 이름 입력 (Mac에서는 Finder 홈 폴더, Windows에서는 C:\사용자 폴더명)
78 |
79 | 2. 다음 코드를 이용하여 설정 파일에 입력
80 | ```json
81 | {
82 | "mcpServers": {
83 | "dart-mcp": {
84 | "command": "uv",
85 | "args": ["--directory", "/Users/{컴퓨터이름}/Downloads/dart-mcp", "run", "dart.py"],
86 | "env": {
87 | "DART_API_KEY": "{DART_API_KEY}"
88 | }
89 | }
90 | }
91 | }
92 | ```
93 |
94 | ### 7. Claude 재시작 및 사용 시작
95 | **설정 파일을 저장하고 Claude 앱을 닫은 후 다시 시작**합니다.
96 | 이제 Claude에게 질문하면 DART API를 호출하여 답변을 제공합니다.
97 |
98 | ## 사용시 주의사항
99 | - 기업명은 공식적으로 상장된 이름으로 제공해야 합니다.
100 | - 코스피, 코스닥 종목만 조사 가능합니다.
101 | - 주가나 시가총액과 같은 실시간 정보들은 앞으로 연동할 계획입니다.
--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | revision = 1
3 | requires-python = ">=3.10"
4 |
5 | [[package]]
6 | name = "annotated-types"
7 | version = "0.7.0"
8 | source = { registry = "https://pypi.org/simple" }
9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
10 | wheels = [
11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
12 | ]
13 |
14 | [[package]]
15 | name = "anyio"
16 | version = "4.9.0"
17 | source = { registry = "https://pypi.org/simple" }
18 | dependencies = [
19 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
20 | { name = "idna" },
21 | { name = "sniffio" },
22 | { name = "typing-extensions", marker = "python_full_version < '3.13'" },
23 | ]
24 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
25 | wheels = [
26 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
27 | ]
28 |
29 | [[package]]
30 | name = "certifi"
31 | version = "2025.1.31"
32 | source = { registry = "https://pypi.org/simple" }
33 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
34 | wheels = [
35 | { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
36 | ]
37 |
38 | [[package]]
39 | name = "click"
40 | version = "8.1.8"
41 | source = { registry = "https://pypi.org/simple" }
42 | dependencies = [
43 | { name = "colorama", marker = "sys_platform == 'win32'" },
44 | ]
45 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
46 | wheels = [
47 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
48 | ]
49 |
50 | [[package]]
51 | name = "colorama"
52 | version = "0.4.6"
53 | source = { registry = "https://pypi.org/simple" }
54 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
55 | wheels = [
56 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
57 | ]
58 |
59 | [[package]]
60 | name = "dart-mcp"
61 | version = "0.1.0"
62 | source = { virtual = "." }
63 | dependencies = [
64 | { name = "httpx" },
65 | { name = "mcp", extra = ["cli"] },
66 | { name = "python-dotenv" },
67 | ]
68 |
69 | [package.metadata]
70 | requires-dist = [
71 | { name = "httpx", specifier = ">=0.28.1" },
72 | { name = "mcp", extras = ["cli"], specifier = ">=1.6.0" },
73 | { name = "python-dotenv", specifier = ">=1.0.0" },
74 | ]
75 |
76 | [[package]]
77 | name = "exceptiongroup"
78 | version = "1.2.2"
79 | source = { registry = "https://pypi.org/simple" }
80 | sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
81 | wheels = [
82 | { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
83 | ]
84 |
85 | [[package]]
86 | name = "h11"
87 | version = "0.14.0"
88 | source = { registry = "https://pypi.org/simple" }
89 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
90 | wheels = [
91 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
92 | ]
93 |
94 | [[package]]
95 | name = "httpcore"
96 | version = "1.0.8"
97 | source = { registry = "https://pypi.org/simple" }
98 | dependencies = [
99 | { name = "certifi" },
100 | { name = "h11" },
101 | ]
102 | sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 }
103 | wheels = [
104 | { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 },
105 | ]
106 |
107 | [[package]]
108 | name = "httpx"
109 | version = "0.28.1"
110 | source = { registry = "https://pypi.org/simple" }
111 | dependencies = [
112 | { name = "anyio" },
113 | { name = "certifi" },
114 | { name = "httpcore" },
115 | { name = "idna" },
116 | ]
117 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
118 | wheels = [
119 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
120 | ]
121 |
122 | [[package]]
123 | name = "httpx-sse"
124 | version = "0.4.0"
125 | source = { registry = "https://pypi.org/simple" }
126 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
127 | wheels = [
128 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
129 | ]
130 |
131 | [[package]]
132 | name = "idna"
133 | version = "3.10"
134 | source = { registry = "https://pypi.org/simple" }
135 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
136 | wheels = [
137 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
138 | ]
139 |
140 | [[package]]
141 | name = "markdown-it-py"
142 | version = "3.0.0"
143 | source = { registry = "https://pypi.org/simple" }
144 | dependencies = [
145 | { name = "mdurl" },
146 | ]
147 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
148 | wheels = [
149 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
150 | ]
151 |
152 | [[package]]
153 | name = "mcp"
154 | version = "1.6.0"
155 | source = { registry = "https://pypi.org/simple" }
156 | dependencies = [
157 | { name = "anyio" },
158 | { name = "httpx" },
159 | { name = "httpx-sse" },
160 | { name = "pydantic" },
161 | { name = "pydantic-settings" },
162 | { name = "sse-starlette" },
163 | { name = "starlette" },
164 | { name = "uvicorn" },
165 | ]
166 | sdist = { url = "https://files.pythonhosted.org/packages/95/d2/f587cb965a56e992634bebc8611c5b579af912b74e04eb9164bd49527d21/mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723", size = 200031 }
167 | wheels = [
168 | { url = "https://files.pythonhosted.org/packages/10/30/20a7f33b0b884a9d14dd3aa94ff1ac9da1479fe2ad66dd9e2736075d2506/mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0", size = 76077 },
169 | ]
170 |
171 | [package.optional-dependencies]
172 | cli = [
173 | { name = "python-dotenv" },
174 | { name = "typer" },
175 | ]
176 |
177 | [[package]]
178 | name = "mdurl"
179 | version = "0.1.2"
180 | source = { registry = "https://pypi.org/simple" }
181 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
182 | wheels = [
183 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
184 | ]
185 |
186 | [[package]]
187 | name = "pydantic"
188 | version = "2.11.3"
189 | source = { registry = "https://pypi.org/simple" }
190 | dependencies = [
191 | { name = "annotated-types" },
192 | { name = "pydantic-core" },
193 | { name = "typing-extensions" },
194 | { name = "typing-inspection" },
195 | ]
196 | sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 }
197 | wheels = [
198 | { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 },
199 | ]
200 |
201 | [[package]]
202 | name = "pydantic-core"
203 | version = "2.33.1"
204 | source = { registry = "https://pypi.org/simple" }
205 | dependencies = [
206 | { name = "typing-extensions" },
207 | ]
208 | sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 }
209 | wheels = [
210 | { url = "https://files.pythonhosted.org/packages/38/ea/5f572806ab4d4223d11551af814d243b0e3e02cc6913def4d1fe4a5ca41c/pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26", size = 2044021 },
211 | { url = "https://files.pythonhosted.org/packages/8c/d1/f86cc96d2aa80e3881140d16d12ef2b491223f90b28b9a911346c04ac359/pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927", size = 1861742 },
212 | { url = "https://files.pythonhosted.org/packages/37/08/fbd2cd1e9fc735a0df0142fac41c114ad9602d1c004aea340169ae90973b/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db", size = 1910414 },
213 | { url = "https://files.pythonhosted.org/packages/7f/73/3ac217751decbf8d6cb9443cec9b9eb0130eeada6ae56403e11b486e277e/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48", size = 1996848 },
214 | { url = "https://files.pythonhosted.org/packages/9a/f5/5c26b265cdcff2661e2520d2d1e9db72d117ea00eb41e00a76efe68cb009/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969", size = 2141055 },
215 | { url = "https://files.pythonhosted.org/packages/5d/14/a9c3cee817ef2f8347c5ce0713e91867a0dceceefcb2973942855c917379/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e", size = 2753806 },
216 | { url = "https://files.pythonhosted.org/packages/f2/68/866ce83a51dd37e7c604ce0050ff6ad26de65a7799df89f4db87dd93d1d6/pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89", size = 2007777 },
217 | { url = "https://files.pythonhosted.org/packages/b6/a8/36771f4404bb3e49bd6d4344da4dede0bf89cc1e01f3b723c47248a3761c/pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde", size = 2122803 },
218 | { url = "https://files.pythonhosted.org/packages/18/9c/730a09b2694aa89360d20756369822d98dc2f31b717c21df33b64ffd1f50/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65", size = 2086755 },
219 | { url = "https://files.pythonhosted.org/packages/54/8e/2dccd89602b5ec31d1c58138d02340ecb2ebb8c2cac3cc66b65ce3edb6ce/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc", size = 2257358 },
220 | { url = "https://files.pythonhosted.org/packages/d1/9c/126e4ac1bfad8a95a9837acdd0963695d69264179ba4ede8b8c40d741702/pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091", size = 2257916 },
221 | { url = "https://files.pythonhosted.org/packages/7d/ba/91eea2047e681a6853c81c20aeca9dcdaa5402ccb7404a2097c2adf9d038/pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383", size = 1923823 },
222 | { url = "https://files.pythonhosted.org/packages/94/c0/fcdf739bf60d836a38811476f6ecd50374880b01e3014318b6e809ddfd52/pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504", size = 1952494 },
223 | { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 },
224 | { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 },
225 | { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 },
226 | { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 },
227 | { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 },
228 | { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 },
229 | { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 },
230 | { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 },
231 | { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 },
232 | { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 },
233 | { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 },
234 | { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 },
235 | { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 },
236 | { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 },
237 | { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 },
238 | { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 },
239 | { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 },
240 | { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 },
241 | { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 },
242 | { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 },
243 | { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 },
244 | { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 },
245 | { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 },
246 | { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 },
247 | { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 },
248 | { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 },
249 | { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 },
250 | { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 },
251 | { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 },
252 | { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 },
253 | { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 },
254 | { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 },
255 | { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 },
256 | { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 },
257 | { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 },
258 | { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 },
259 | { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 },
260 | { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 },
261 | { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 },
262 | { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 },
263 | { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 },
264 | { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 },
265 | { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 },
266 | { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 },
267 | { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 },
268 | { url = "https://files.pythonhosted.org/packages/9c/c7/8b311d5adb0fe00a93ee9b4e92a02b0ec08510e9838885ef781ccbb20604/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02", size = 2041659 },
269 | { url = "https://files.pythonhosted.org/packages/8a/d6/4f58d32066a9e26530daaf9adc6664b01875ae0691570094968aaa7b8fcc/pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068", size = 1873294 },
270 | { url = "https://files.pythonhosted.org/packages/f7/3f/53cc9c45d9229da427909c751f8ed2bf422414f7664ea4dde2d004f596ba/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e", size = 1903771 },
271 | { url = "https://files.pythonhosted.org/packages/f0/49/bf0783279ce674eb9903fb9ae43f6c614cb2f1c4951370258823f795368b/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe", size = 2083558 },
272 | { url = "https://files.pythonhosted.org/packages/9c/5b/0d998367687f986c7d8484a2c476d30f07bf5b8b1477649a6092bd4c540e/pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1", size = 2118038 },
273 | { url = "https://files.pythonhosted.org/packages/b3/33/039287d410230ee125daee57373ac01940d3030d18dba1c29cd3089dc3ca/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7", size = 2079315 },
274 | { url = "https://files.pythonhosted.org/packages/1f/85/6d8b2646d99c062d7da2d0ab2faeb0d6ca9cca4c02da6076376042a20da3/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde", size = 2249063 },
275 | { url = "https://files.pythonhosted.org/packages/17/d7/c37d208d5738f7b9ad8f22ae8a727d88ebf9c16c04ed2475122cc3f7224a/pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add", size = 2254631 },
276 | { url = "https://files.pythonhosted.org/packages/13/e0/bafa46476d328e4553b85ab9b2f7409e7aaef0ce4c937c894821c542d347/pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c", size = 2080877 },
277 | { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 },
278 | { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 },
279 | { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 },
280 | { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 },
281 | { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 },
282 | { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 },
283 | { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 },
284 | { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 },
285 | { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 },
286 | ]
287 |
288 | [[package]]
289 | name = "pydantic-settings"
290 | version = "2.8.1"
291 | source = { registry = "https://pypi.org/simple" }
292 | dependencies = [
293 | { name = "pydantic" },
294 | { name = "python-dotenv" },
295 | ]
296 | sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550 }
297 | wheels = [
298 | { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839 },
299 | ]
300 |
301 | [[package]]
302 | name = "pygments"
303 | version = "2.19.1"
304 | source = { registry = "https://pypi.org/simple" }
305 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
306 | wheels = [
307 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
308 | ]
309 |
310 | [[package]]
311 | name = "python-dotenv"
312 | version = "1.1.0"
313 | source = { registry = "https://pypi.org/simple" }
314 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
315 | wheels = [
316 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
317 | ]
318 |
319 | [[package]]
320 | name = "rich"
321 | version = "14.0.0"
322 | source = { registry = "https://pypi.org/simple" }
323 | dependencies = [
324 | { name = "markdown-it-py" },
325 | { name = "pygments" },
326 | { name = "typing-extensions", marker = "python_full_version < '3.11'" },
327 | ]
328 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
329 | wheels = [
330 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
331 | ]
332 |
333 | [[package]]
334 | name = "shellingham"
335 | version = "1.5.4"
336 | source = { registry = "https://pypi.org/simple" }
337 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
338 | wheels = [
339 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
340 | ]
341 |
342 | [[package]]
343 | name = "sniffio"
344 | version = "1.3.1"
345 | source = { registry = "https://pypi.org/simple" }
346 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
347 | wheels = [
348 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
349 | ]
350 |
351 | [[package]]
352 | name = "sse-starlette"
353 | version = "2.2.1"
354 | source = { registry = "https://pypi.org/simple" }
355 | dependencies = [
356 | { name = "anyio" },
357 | { name = "starlette" },
358 | ]
359 | sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376 }
360 | wheels = [
361 | { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120 },
362 | ]
363 |
364 | [[package]]
365 | name = "starlette"
366 | version = "0.46.2"
367 | source = { registry = "https://pypi.org/simple" }
368 | dependencies = [
369 | { name = "anyio" },
370 | ]
371 | sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 }
372 | wheels = [
373 | { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 },
374 | ]
375 |
376 | [[package]]
377 | name = "typer"
378 | version = "0.15.2"
379 | source = { registry = "https://pypi.org/simple" }
380 | dependencies = [
381 | { name = "click" },
382 | { name = "rich" },
383 | { name = "shellingham" },
384 | { name = "typing-extensions" },
385 | ]
386 | sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 }
387 | wheels = [
388 | { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 },
389 | ]
390 |
391 | [[package]]
392 | name = "typing-extensions"
393 | version = "4.13.2"
394 | source = { registry = "https://pypi.org/simple" }
395 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
396 | wheels = [
397 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
398 | ]
399 |
400 | [[package]]
401 | name = "typing-inspection"
402 | version = "0.4.0"
403 | source = { registry = "https://pypi.org/simple" }
404 | dependencies = [
405 | { name = "typing-extensions" },
406 | ]
407 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
408 | wheels = [
409 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
410 | ]
411 |
412 | [[package]]
413 | name = "uvicorn"
414 | version = "0.34.1"
415 | source = { registry = "https://pypi.org/simple" }
416 | dependencies = [
417 | { name = "click" },
418 | { name = "h11" },
419 | { name = "typing-extensions", marker = "python_full_version < '3.11'" },
420 | ]
421 | sdist = { url = "https://files.pythonhosted.org/packages/86/37/dd92f1f9cedb5eaf74d9999044306e06abe65344ff197864175dbbd91871/uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc", size = 76755 }
422 | wheels = [
423 | { url = "https://files.pythonhosted.org/packages/5f/38/a5801450940a858c102a7ad9e6150146a25406a119851c993148d56ab041/uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065", size = 62404 },
424 | ]
425 |
--------------------------------------------------------------------------------
/dart.py:
--------------------------------------------------------------------------------
1 | import httpx
2 | from typing import Any, Dict, List, Optional, Tuple, Set
3 | from mcp.server.fastmcp import FastMCP, Context
4 | import os
5 | import zipfile
6 | import xml.etree.ElementTree as ET
7 | from io import BytesIO, StringIO
8 | import re
9 | import traceback
10 | from datetime import datetime, timedelta
11 | from dotenv import load_dotenv
12 |
13 |
14 | load_dotenv()
15 | # 상수 정의
16 | # API 설정
17 | API_KEY = os.environ.get("DART_API_KEY") # 환경 변수에서 API 키 로드, 없으면 기본값 사용
18 | BASE_URL = "https://opendart.fss.or.kr/api"
19 |
20 | # 보고서 코드
21 | REPORT_CODE = {
22 | "사업보고서": "11011",
23 | "반기보고서": "11012",
24 | "1분기보고서": "11013",
25 | "3분기보고서": "11014"
26 | }
27 |
28 | # 재무상태표 항목 리스트 - 확장
29 | BALANCE_SHEET_ITEMS = [
30 | "유동자산", "비유동자산", "자산총계",
31 | "유동부채", "비유동부채", "부채총계",
32 | "자본금", "자본잉여금", "이익잉여금", "기타자본항목", "자본총계"
33 | ]
34 |
35 | # 현금흐름표 항목 리스트
36 | CASH_FLOW_ITEMS = ["영업활동 현금흐름", "투자활동 현금흐름", "재무활동 현금흐름"]
37 |
38 | # 보고서 유형별 contextRef 패턴 정의
39 | REPORT_PATTERNS = {
40 | "연간": "FY",
41 | "3분기": "TQQ", # 손익계산서는 TQQ
42 | "반기": "HYA",
43 | "1분기": "FQA"
44 | }
45 |
46 | # 현금흐름표용 특별 패턴
47 | CASH_FLOW_PATTERNS = {
48 | "연간": "FY",
49 | "3분기": "TQA", # 현금흐름표는 TQA
50 | "반기": "HYA",
51 | "1분기": "FQA"
52 | }
53 |
54 | # 재무상태표용 특별 패턴
55 | BALANCE_SHEET_PATTERNS = {
56 | "연간": "FY",
57 | "3분기": "TQA", # 재무상태표도 TQA
58 | "반기": "HYA",
59 | "1분기": "FQA"
60 | }
61 |
62 | # 데이터 무효/오류 상태 표시자
63 | INVALID_VALUE_INDICATORS = {"N/A", "XBRL 파싱 오류", "데이터 추출 오류"}
64 |
65 | # MCP 서버 초기화
66 | mcp = FastMCP("dart")
67 |
68 | # 재무제표 유형 정의
69 | STATEMENT_TYPES = {
70 | "재무상태표": "BS",
71 | "손익계산서": "IS",
72 | "현금흐름표": "CF"
73 | }
74 |
75 | # 세부 항목 태그 정의
76 | DETAILED_TAGS = {
77 | "재무상태표": {
78 | "유동자산": ["ifrs-full:CurrentAssets"],
79 | "비유동자산": ["ifrs-full:NoncurrentAssets"],
80 | "자산총계": ["ifrs-full:Assets"],
81 | "유동부채": ["ifrs-full:CurrentLiabilities"],
82 | "비유동부채": ["ifrs-full:NoncurrentLiabilities"],
83 | "부채총계": ["ifrs-full:Liabilities"],
84 | "자본금": ["ifrs-full:IssuedCapital"],
85 | "자본잉여금": ["ifrs-full:SharePremium"],
86 | "이익잉여금": ["ifrs-full:RetainedEarnings"],
87 | "기타자본항목": ["dart:ElementsOfOtherStockholdersEquity"],
88 | "자본총계": ["ifrs-full:Equity"]
89 | },
90 | "손익계산서": {
91 | "매출액": ["ifrs-full:Revenue"],
92 | "매출원가": ["ifrs-full:CostOfSales"],
93 | "매출총이익": ["ifrs-full:GrossProfit"],
94 | "판매비와관리비": ["dart:TotalSellingGeneralAdministrativeExpenses"],
95 | "영업이익": ["dart:OperatingIncomeLoss"],
96 | "금융수익": ["ifrs-full:FinanceIncome"],
97 | "금융비용": ["ifrs-full:FinanceCosts"],
98 | "법인세비용차감전순이익": ["ifrs-full:ProfitLossBeforeTax"],
99 | "법인세비용": ["ifrs-full:IncomeTaxExpenseContinuingOperations"],
100 | "당기순이익": ["ifrs-full:ProfitLoss"],
101 | "기본주당이익": ["ifrs-full:BasicEarningsLossPerShare"]
102 | },
103 | "현금흐름표": {
104 | "영업활동 현금흐름": ["ifrs-full:CashFlowsFromUsedInOperatingActivities"],
105 | "영업에서 창출된 현금": ["ifrs-full:CashFlowsFromUsedInOperations"],
106 | "이자수취": ["ifrs-full:InterestReceivedClassifiedAsOperatingActivities"],
107 | "이자지급": ["ifrs-full:InterestPaidClassifiedAsOperatingActivities"],
108 | "배당금수취": ["ifrs-full:DividendsReceivedClassifiedAsOperatingActivities"],
109 | "법인세납부": ["ifrs-full:IncomeTaxesPaidRefundClassifiedAsOperatingActivities"],
110 | "투자활동 현금흐름": ["ifrs-full:CashFlowsFromUsedInInvestingActivities"],
111 | "유형자산의 취득": ["ifrs-full:PurchaseOfPropertyPlantAndEquipmentClassifiedAsInvestingActivities"],
112 | "무형자산의 취득": ["ifrs-full:PurchaseOfIntangibleAssetsClassifiedAsInvestingActivities"],
113 | "유형자산의 처분": ["ifrs-full:ProceedsFromSalesOfPropertyPlantAndEquipmentClassifiedAsInvestingActivities"],
114 | "재무활동 현금흐름": ["ifrs-full:CashFlowsFromUsedInFinancingActivities"],
115 | "배당금지급": ["ifrs-full:DividendsPaidClassifiedAsFinancingActivities"],
116 | "현금및현금성자산의순증가": ["ifrs-full:IncreaseDecreaseInCashAndCashEquivalents"],
117 | "기초현금및현금성자산": ["dart:CashAndCashEquivalentsAtBeginningOfPeriodCf"],
118 | "기말현금및현금성자산": ["dart:CashAndCashEquivalentsAtEndOfPeriodCf"]
119 | }
120 | }
121 |
122 | chat_guideline = "\n* 제공된 공시정보들은 분기, 반기, 연간이 섞여있을 수 있습니다. \n사용자가 특별히 연간이나 반기데이터만을 원하는게 아니라면, 주어진 데이터를 적당히 가공하여 분기별로 사용자에게 제공하세요." ;
123 |
124 |
125 | # Helper 함수
126 |
127 | async def get_corp_code_by_name(corp_name: str) -> Tuple[str, str]:
128 | """
129 | 회사명으로 회사의 고유번호를 검색하는 함수
130 |
131 | Args:
132 | corp_name: 검색할 회사명
133 |
134 | Returns:
135 | (고유번호, 기업이름) 튜플, 찾지 못한 경우 ("", "")
136 | """
137 | url = f"{BASE_URL}/corpCode.xml?crtfc_key={API_KEY}"
138 |
139 | try:
140 | async with httpx.AsyncClient() as client:
141 | try:
142 | response = await client.get(url)
143 |
144 | if response.status_code != 200:
145 | return ("", f"API 요청 실패: HTTP 상태 코드 {response.status_code}")
146 |
147 | try:
148 | with zipfile.ZipFile(BytesIO(response.content)) as zip_file:
149 | try:
150 | with zip_file.open('CORPCODE.xml') as xml_file:
151 | try:
152 | tree = ET.parse(xml_file)
153 | root = tree.getroot()
154 |
155 | # 검색어를 포함하는 모든 회사 찾기
156 | matches = []
157 | for company in root.findall('.//list'):
158 | name = company.find('corp_name').text
159 | stock_code = company.find('stock_code').text
160 |
161 | # stock_code가 비어있거나 공백만 있는 경우 건너뛰기
162 | if not stock_code or stock_code.strip() == "":
163 | continue
164 |
165 | if name and corp_name in name:
166 | # 일치도 점수 계산 (낮을수록 더 정확히 일치)
167 | score = 0
168 | if name != corp_name:
169 | score += abs(len(name) - len(corp_name))
170 | if not name.startswith(corp_name):
171 | score += 10
172 |
173 | code = company.find('corp_code').text
174 | matches.append((name, code, score))
175 |
176 | # 일치하는 회사가 없는 경우
177 | if not matches:
178 | return ("", f"'{corp_name}' 회사를 찾을 수 없습니다.")
179 |
180 | # 일치도 점수가 가장 낮은 (가장 일치하는) 회사 반환
181 | matches.sort(key=lambda x: x[2])
182 | matched_name = matches[0][0]
183 | matched_code = matches[0][1]
184 | return (matched_code, matched_name)
185 | except ET.ParseError as e:
186 | return ("", f"XML 파싱 오류: {str(e)}")
187 | except Exception as e:
188 | return ("", f"ZIP 파일 내부 파일 접근 오류: {str(e)}")
189 | except zipfile.BadZipFile:
190 | return ("", "다운로드한 파일이 유효한 ZIP 파일이 아닙니다.")
191 | except Exception as e:
192 | return ("", f"ZIP 파일 처리 중 오류 발생: {str(e)}")
193 | except httpx.RequestError as e:
194 | return ("", f"API 요청 중 네트워크 오류 발생: {str(e)}")
195 | except Exception as e:
196 | return ("", f"회사 코드 조회 중 예상치 못한 오류 발생: {str(e)}")
197 |
198 | return ("", "알 수 없는 오류로 회사 정보를 찾을 수 없습니다.")
199 |
200 |
201 | async def get_disclosure_list(corp_code: str, start_date: str, end_date: str) -> Tuple[List[Dict[str, Any]], Optional[str]]:
202 | """
203 | 기업의 정기공시 목록을 조회하는 함수
204 |
205 | Args:
206 | corp_code: 회사 고유번호(8자리)
207 | start_date: 시작일(YYYYMMDD)
208 | end_date: 종료일(YYYYMMDD)
209 |
210 | Returns:
211 | (공시 목록 리스트, 오류 메시지) 튜플. 성공 시 (목록, None), 실패 시 (빈 리스트, 오류 메시지)
212 | """
213 | # 정기공시(A) 유형만 조회
214 | url = f"{BASE_URL}/list.json?crtfc_key={API_KEY}&corp_code={corp_code}&bgn_de={start_date}&end_de={end_date}&pblntf_ty=A&page_count=100"
215 |
216 | try:
217 | async with httpx.AsyncClient() as client:
218 | try:
219 | response = await client.get(url)
220 |
221 | if response.status_code != 200:
222 | return [], f"API 요청 실패: HTTP 상태 코드 {response.status_code}"
223 |
224 | try:
225 | result = response.json()
226 |
227 | if result.get('status') != '000':
228 | status = result.get('status', '알 수 없음')
229 | msg = result.get('message', '알 수 없는 오류')
230 | return [], f"DART API 오류: {status} - {msg}"
231 |
232 | return result.get('list', []), None
233 | except Exception as e:
234 | return [], f"응답 JSON 파싱 오류: {str(e)}"
235 | except httpx.RequestError as e:
236 | return [], f"API 요청 중 네트워크 오류 발생: {str(e)}"
237 | except Exception as e:
238 | return [], f"공시 목록 조회 중 예상치 못한 오류 발생: {str(e)}"
239 |
240 | return [], "알 수 없는 오류로 공시 목록을 조회할 수 없습니다."
241 |
242 |
243 | async def get_financial_statement_xbrl(rcept_no: str, reprt_code: str) -> str:
244 | """
245 | 재무제표 원본파일(XBRL)을 다운로드하여 XBRL 텍스트를 반환하는 함수
246 |
247 | Args:
248 | rcept_no: 공시 접수번호(14자리)
249 | reprt_code: 보고서 코드 (11011: 사업보고서, 11012: 반기보고서, 11013: 1분기보고서, 11014: 3분기보고서)
250 |
251 | Returns:
252 | 추출된 XBRL 텍스트 내용, 실패 시 오류 메시지 문자열
253 | """
254 | url = f"{BASE_URL}/fnlttXbrl.xml?crtfc_key={API_KEY}&rcept_no={rcept_no}&reprt_code={reprt_code}"
255 |
256 | try:
257 | async with httpx.AsyncClient(timeout=30.0) as client:
258 | response = await client.get(url)
259 |
260 | if response.status_code != 200:
261 | return f"API 요청 실패: HTTP 상태 코드 {response.status_code}"
262 |
263 | try:
264 | with zipfile.ZipFile(BytesIO(response.content)) as zip_file:
265 | xbrl_content = ""
266 | for file_name in zip_file.namelist():
267 | if file_name.lower().endswith('.xbrl'):
268 | with zip_file.open(file_name) as xbrl_file:
269 | # XBRL 파일을 텍스트로 읽기 (UTF-8 시도, 실패 시 EUC-KR)
270 | try:
271 | xbrl_content = xbrl_file.read().decode('utf-8')
272 | except UnicodeDecodeError:
273 | try:
274 | xbrl_file.seek(0)
275 | xbrl_content = xbrl_file.read().decode('euc-kr')
276 | except UnicodeDecodeError:
277 | xbrl_content = "<인코딩 오류: XBRL 내용을 읽을 수 없습니다>"
278 | break
279 |
280 | if not xbrl_content:
281 | return "ZIP 파일 내에서 XBRL 파일을 찾을 수 없습니다."
282 |
283 | return xbrl_content
284 |
285 | except zipfile.BadZipFile:
286 | # 응답이 ZIP 파일 형식이 아닐 경우 (DART API 오류 메시지 등)
287 | try:
288 | error_content = response.content.decode('utf-8')
289 | try:
290 | root = ET.fromstring(error_content)
291 | status = root.findtext('status')
292 | message = root.findtext('message')
293 | if status and message:
294 | return f"DART API 오류: {status} - {message}"
295 | else:
296 | return f"유효하지 않은 ZIP 파일이며, 오류 메시지 파싱 실패: {error_content[:200]}"
297 | except ET.ParseError:
298 | return f"유효하지 않은 ZIP 파일이며, XML 파싱 불가: {error_content[:200]}"
299 | except Exception:
300 | return "다운로드한 파일이 유효한 ZIP 파일이 아닙니다 (내용 확인 불가)."
301 | except Exception as e:
302 | return f"ZIP 파일 처리 중 오류 발생: {str(e)}"
303 |
304 | except httpx.RequestError as e:
305 | return f"API 요청 중 네트워크 오류 발생: {str(e)}"
306 | except Exception as e:
307 | return f"XBRL 데이터 처리 중 예상치 못한 오류 발생: {str(e)}"
308 |
309 |
310 | def detect_namespaces(xbrl_content: str, base_namespaces: Dict[str, str]) -> Dict[str, str]:
311 | """
312 | XBRL 문서에서 네임스페이스를 추출하고 기본 네임스페이스와 병합
313 |
314 | Args:
315 | xbrl_content: XBRL 문서 내용
316 | base_namespaces: 기본 네임스페이스 딕셔너리
317 |
318 | Returns:
319 | 업데이트된 네임스페이스 딕셔너리
320 | """
321 | namespaces = base_namespaces.copy()
322 | detected = {}
323 |
324 | try:
325 | for event, node in ET.iterparse(StringIO(xbrl_content), events=['start-ns']):
326 | prefix, uri = node
327 | if prefix and prefix not in namespaces:
328 | namespaces[prefix] = uri
329 | detected[prefix] = uri
330 | elif prefix and namespaces.get(prefix) != uri:
331 | namespaces[prefix] = uri
332 | detected[prefix] = uri
333 | except Exception:
334 | pass # 네임스페이스 감지 실패 시 기본값 사용
335 |
336 | return namespaces, detected
337 |
338 |
339 | def extract_fiscal_year(context_refs: Set[str]) -> str:
340 | """
341 | contextRef 집합에서 회계연도 추출
342 |
343 | Args:
344 | context_refs: XBRL 문서에서 추출한 contextRef 집합
345 |
346 | Returns:
347 | 감지된 회계연도 또는 현재 연도
348 | """
349 | for context_ref in context_refs:
350 | if 'CFY' in context_ref and len(context_ref) > 7:
351 | match = re.search(r'CFY(\d{4})', context_ref)
352 | if match:
353 | return match.group(1)
354 |
355 | # 회계연도를 찾지 못한 경우, 현재 연도를 사용
356 | return str(datetime.now().year)
357 |
358 |
359 | def get_pattern_by_item_type(item_name: str) -> Dict[str, str]:
360 | """
361 | 항목 유형에 따른 적절한 패턴 선택
362 |
363 | Args:
364 | item_name: 재무 항목 이름
365 |
366 | Returns:
367 | 항목 유형에 맞는 패턴 딕셔너리
368 | """
369 | # 현금흐름표 항목 확인
370 | if item_name in CASH_FLOW_ITEMS or item_name in DETAILED_TAGS["현금흐름표"]:
371 | return CASH_FLOW_PATTERNS
372 |
373 | # 재무상태표 항목 확인
374 | elif item_name in BALANCE_SHEET_ITEMS or item_name in DETAILED_TAGS["재무상태표"]:
375 | return BALANCE_SHEET_PATTERNS
376 |
377 | # 손익계산서 항목 (기본값)
378 | else:
379 | return REPORT_PATTERNS
380 |
381 |
382 | def format_numeric_value(value_text: str, decimals: str) -> str:
383 | """
384 | XBRL 숫자 값을 포맷팅
385 |
386 | Args:
387 | value_text: 숫자 텍스트
388 | decimals: 소수점 자리수 지정 (숫자 또는 "INF")
389 |
390 | Returns:
391 | 포맷팅된 숫자 문자열
392 | """
393 | numeric_value = float(value_text.replace(',', ''))
394 |
395 | # decimals가 "INF"인 경우 원본 값 그대로 사용
396 | if decimals == "INF":
397 | if numeric_value == int(numeric_value):
398 | return f"{int(numeric_value):,}"
399 | else:
400 | return f"{numeric_value:,.2f}"
401 |
402 | # 일반적인 경우 decimals에 따라 스케일 조정
403 | numeric_value *= (10 ** -int(decimals))
404 |
405 | if numeric_value == int(numeric_value):
406 | return f"{int(numeric_value):,}"
407 | else:
408 | return f"{numeric_value:,.2f}"
409 |
410 |
411 | def parse_xbrl_financial_data(xbrl_content: str, items_and_tags: Dict[str, List[str]]) -> Dict[str, str]:
412 | """
413 | XBRL 텍스트 내용을 파싱하여 지정된 항목의 재무 데이터를 추출
414 |
415 | Args:
416 | xbrl_content: XBRL 파일의 전체 텍스트 내용
417 | items_and_tags: 추출할 항목과 태그 리스트 딕셔너리
418 | {'항목명': ['태그1', '태그2', ...]}
419 |
420 | Returns:
421 | 추출된 재무 데이터 딕셔너리 {'항목명': '값'}
422 | """
423 | extracted_data = {item_name: "N/A" for item_name in items_and_tags}
424 |
425 | # 기본 네임스페이스 정의
426 | base_namespaces = {
427 | 'ifrs-full': 'http://xbrl.ifrs.org/taxonomy/2021-03-24/ifrs-full',
428 | 'dart': 'http://dart.fss.or.kr/xbrl/dte/2019-10-31',
429 | 'kor-ifrs': 'http://www.fss.or.kr/xbrl/kor/kor-ifrs/2021-03-24',
430 | }
431 |
432 | try:
433 | # XBRL 파싱
434 | root = ET.fromstring(xbrl_content)
435 |
436 | # 네임스페이스 추출 및 업데이트
437 | namespaces, detected_namespaces = detect_namespaces(xbrl_content, base_namespaces)
438 |
439 | # 모든 contextRef 값 수집
440 | all_context_refs = set()
441 | for elem in root.findall('.//*[@contextRef]'):
442 | all_context_refs.add(elem.get('contextRef'))
443 |
444 | # 회계연도 추출
445 | fiscal_year = extract_fiscal_year(all_context_refs)
446 |
447 | # 각 항목별 태그 검색 및 값 추출
448 | for item_name, tag_list in items_and_tags.items():
449 | item_found = False
450 |
451 | for tag in tag_list:
452 | if item_found:
453 | break
454 |
455 | # 해당 태그 요소 검색
456 | elements = root.findall(f'.//{tag}', namespaces)
457 | if not elements:
458 | continue
459 |
460 | # 항목 유형에 맞는 패턴 선택
461 | patterns = get_pattern_by_item_type(item_name)
462 |
463 | # 각 보고서 유형별 패턴 시도
464 | for report_type, pattern_code in patterns.items():
465 | if item_found:
466 | break
467 |
468 | # 기존 접두사 로직은 참조용으로만 사용 (실제 패턴 매칭에는 사용하지 않음)
469 | # 패턴에서 접두사 부분을 (.): 어떤 한 글자라도 매칭되도록 함
470 | pattern_base = f"CFY{fiscal_year}.{pattern_code}_ifrs-full_ConsolidatedAndSeparateFinancialStatementsAxis_ifrs-full_ConsolidatedMember"
471 | # 패턴의 끝에 $ 추가하여 정확히 일치하는 패턴만 매칭
472 | pattern_regex = re.compile(f"^{pattern_base}$")
473 |
474 | # 패턴과 일치하는 요소 찾기
475 | for elem in elements:
476 | context_ref = elem.get('contextRef')
477 |
478 | # 정규식으로 패턴 매칭 확인 (완전 일치)
479 | if context_ref and pattern_regex.match(context_ref):
480 | unit_ref = elem.get('unitRef')
481 | value_text = elem.text
482 | decimals = elem.get('decimals', '0')
483 |
484 | if value_text and unit_ref:
485 | try:
486 | formatted_value = format_numeric_value(value_text, decimals)
487 | extracted_data[item_name] = f"{formatted_value} ({report_type})"
488 | item_found = True
489 | break
490 | except (ValueError, TypeError) as e:
491 | pass
492 |
493 | if item_found:
494 | break
495 |
496 | except ET.ParseError as e:
497 | extracted_data = {key: "XBRL 파싱 오류" for key in items_and_tags}
498 | except Exception as e:
499 | traceback.print_exc()
500 | extracted_data = {key: "데이터 추출 오류" for key in items_and_tags}
501 |
502 | return extracted_data
503 |
504 |
505 | def determine_report_code(report_name: str) -> Optional[str]:
506 | """
507 | 보고서 이름으로부터 보고서 코드 결정
508 |
509 | Args:
510 | report_name: 보고서 이름
511 |
512 | Returns:
513 | 해당하는 보고서 코드 또는 None
514 | """
515 | if "사업보고서" in report_name:
516 | return REPORT_CODE["사업보고서"]
517 | elif "반기보고서" in report_name:
518 | return REPORT_CODE["반기보고서"]
519 | elif "분기보고서" in report_name:
520 | if ".03)" in report_name or "(1분기)" in report_name:
521 | return REPORT_CODE["1분기보고서"]
522 | elif ".09)" in report_name or "(3분기)" in report_name:
523 | return REPORT_CODE["3분기보고서"]
524 |
525 | return None
526 |
527 |
528 | def adjust_end_date(end_date: str) -> Tuple[str, bool]:
529 | """
530 | 공시 제출 기간을 고려하여 종료일 조정
531 |
532 | Args:
533 | end_date: 원래 종료일 (YYYYMMDD)
534 |
535 | Returns:
536 | 조정된 종료일과 조정 여부
537 | """
538 | try:
539 | # 입력된 end_date를 datetime 객체로 변환
540 | end_date_obj = datetime.strptime(end_date, "%Y%m%d")
541 |
542 | # 95일 추가
543 | adjusted_end_date_obj = end_date_obj + timedelta(days=95)
544 |
545 | # 현재 날짜보다 미래인 경우 현재 날짜로 조정
546 | current_date = datetime.now()
547 | if adjusted_end_date_obj > current_date:
548 | adjusted_end_date_obj = current_date
549 |
550 | # 포맷 변환하여 문자열로 반환
551 | adjusted_end_date = adjusted_end_date_obj.strftime("%Y%m%d")
552 |
553 | # 조정 여부 반환
554 | return adjusted_end_date, adjusted_end_date != end_date
555 | except Exception:
556 | # 오류 발생 시 원래 값 그대로 반환
557 | return end_date, False
558 |
559 |
560 | def extract_business_section(document_text: str, section_type: str) -> str:
561 | """
562 | 공시서류 원본파일 텍스트에서 특정 비즈니스 섹션만 추출하는 함수
563 |
564 | Args:
565 | document_text: 공시서류 원본 텍스트
566 | section_type: 추출할 섹션 유형
567 | ('사업의 개요', '주요 제품 및 서비스', '원재료 및 생산설비',
568 | '매출 및 수주상황', '위험관리 및 파생거래', '주요계약 및 연구개발활동',
569 | '기타 참고사항')
570 |
571 | Returns:
572 | 추출된 섹션 텍스트 (태그 제거 및 정리된 상태)
573 | """
574 | import re
575 |
576 | # SECTION 태그 형식 확인
577 | section_tags = re.findall(r'