fisis 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. fisis-0.1.0/.agents/plugins/marketplace.json +20 -0
  2. fisis-0.1.0/.claude-plugin/marketplace.json +27 -0
  3. fisis-0.1.0/.github/workflows/check.yml +25 -0
  4. fisis-0.1.0/.github/workflows/publish.yml +61 -0
  5. fisis-0.1.0/.github/workflows/reusable-check.yml +98 -0
  6. fisis-0.1.0/.gitignore +33 -0
  7. fisis-0.1.0/LICENSE +21 -0
  8. fisis-0.1.0/PKG-INFO +364 -0
  9. fisis-0.1.0/README.en.md +341 -0
  10. fisis-0.1.0/README.md +329 -0
  11. fisis-0.1.0/plugins/fisis/.claude-plugin/plugin.json +19 -0
  12. fisis-0.1.0/plugins/fisis/.codex-plugin/plugin.json +33 -0
  13. fisis-0.1.0/plugins/fisis/skills/accounts/SKILL.md +64 -0
  14. fisis-0.1.0/plugins/fisis/skills/companies/SKILL.md +64 -0
  15. fisis-0.1.0/plugins/fisis/skills/data/SKILL.md +79 -0
  16. fisis-0.1.0/plugins/fisis/skills/statistics/SKILL.md +67 -0
  17. fisis-0.1.0/pyproject.toml +71 -0
  18. fisis-0.1.0/src/fisis/__init__.py +56 -0
  19. fisis-0.1.0/src/fisis/__main__.py +8 -0
  20. fisis-0.1.0/src/fisis/_accessor.py +228 -0
  21. fisis-0.1.0/src/fisis/_companies.py +567 -0
  22. fisis-0.1.0/src/fisis/_config.py +63 -0
  23. fisis-0.1.0/src/fisis/_parse.py +146 -0
  24. fisis-0.1.0/src/fisis/_transport.py +170 -0
  25. fisis-0.1.0/src/fisis/cli.py +294 -0
  26. fisis-0.1.0/src/fisis/client.py +313 -0
  27. fisis-0.1.0/src/fisis/exceptions.py +78 -0
  28. fisis-0.1.0/src/fisis/py.typed +0 -0
  29. fisis-0.1.0/src/fisis/types.py +224 -0
  30. fisis-0.1.0/tests/test_accessor.py +224 -0
  31. fisis-0.1.0/tests/test_cli.py +305 -0
  32. fisis-0.1.0/tests/test_client.py +535 -0
  33. fisis-0.1.0/tests/test_companies.py +245 -0
  34. fisis-0.1.0/tests/test_config.py +68 -0
  35. fisis-0.1.0/tests/test_parse.py +182 -0
  36. fisis-0.1.0/tests/test_types.py +68 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "fisis",
3
+ "interface": {
4
+ "displayName": "fisis"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "fisis",
9
+ "source": {
10
+ "source": "local",
11
+ "path": "./plugins/fisis"
12
+ },
13
+ "policy": {
14
+ "installation": "AVAILABLE",
15
+ "authentication": "ON_INSTALL"
16
+ },
17
+ "category": "Productivity"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "fisis",
3
+ "owner": {
4
+ "name": "seokhoonj"
5
+ },
6
+ "description": "Read FSS FISIS financial-institution statistics from the command line",
7
+ "plugins": [
8
+ {
9
+ "name": "fisis",
10
+ "displayName": "fisis",
11
+ "source": "./plugins/fisis",
12
+ "description": "Read FSS FISIS financial-institution statistics from the command line",
13
+ "author": {
14
+ "name": "seokhoonj"
15
+ },
16
+ "homepage": "https://github.com/seokhoonj/fisis",
17
+ "category": "Productivity",
18
+ "keywords": [
19
+ "fisis",
20
+ "fss",
21
+ "financial statistics",
22
+ "korea",
23
+ "finance"
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,25 @@
1
+ name: check
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+
9
+ # A newer push to the same branch or PR makes the older run's result obsolete,
10
+ # so cancel it rather than let both run to completion. The event name is in the
11
+ # key so a push never cancels a pull-request run, or vice versa.
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ # The check only reads the code to lint, type, and test it -- it never writes to the
17
+ # repo -- so it runs with the least privilege that still allows a checkout.
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ check:
23
+ # The full check lives in reusable-check.yml so a future release gate can run the
24
+ # identical job on the release commit.
25
+ uses: ./.github/workflows/reusable-check.yml
@@ -0,0 +1,61 @@
1
+ name: publish
2
+
3
+ # Publish to PyPI on a GitHub Release, via PyPI Trusted Publishing (OIDC): no API
4
+ # token or password is stored anywhere -- GitHub mints a short-lived identity token
5
+ # that PyPI verifies against the publisher registered for this repo (owner seokhoonj
6
+ # / repo fisis / this workflow / environment "pypi").
7
+ #
8
+ # Three jobs. `gate` runs the same reusable check that check.yml runs, on the
9
+ # release commit, so a red build (a lint/type error, a broken test, a lost py.typed,
10
+ # console script, or runtime dependency) cannot reach an upload. `build` builds the
11
+ # sdist and wheel and hands them to `publish` as an artifact. `publish` -- the only
12
+ # job holding the OIDC token -- downloads that artifact and uploads it, and checks
13
+ # out no source, so the privileged step runs the least code.
14
+
15
+ on:
16
+ release:
17
+ types: [published]
18
+
19
+ # Workflow-level floor: any job without its own `permissions:` block still gets a
20
+ # read-only token, so a job added later cannot silently inherit a write-capable
21
+ # default. The build and publish jobs narrow it further below.
22
+ permissions:
23
+ contents: read
24
+
25
+ jobs:
26
+ gate:
27
+ uses: ./.github/workflows/reusable-check.yml
28
+
29
+ build:
30
+ needs: gate
31
+ runs-on: ubuntu-latest
32
+ timeout-minutes: 15
33
+ permissions:
34
+ contents: read
35
+ steps:
36
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37
+ with:
38
+ persist-credentials: false
39
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
40
+ - name: Build sdist and wheel, then check the metadata
41
+ run: |
42
+ uv build
43
+ uvx twine check dist/*
44
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+
49
+ publish:
50
+ needs: build
51
+ runs-on: ubuntu-latest
52
+ timeout-minutes: 15
53
+ environment: pypi
54
+ permissions:
55
+ id-token: write # OIDC; this is what replaces a stored token
56
+ steps:
57
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
58
+ with:
59
+ name: dist
60
+ path: dist/
61
+ - uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
@@ -0,0 +1,98 @@
1
+ name: reusable check
2
+
3
+ # The full check -- tests, lint, types, and the packaging assertions -- factored
4
+ # into one reusable job so both `check.yml` (on push / PR) and the release gate in
5
+ # `publish.yml` run the *identical* thing. One definition is what makes "publish
6
+ # gates on the full check" true; a second hand-maintained copy would drift, and a
7
+ # check added here silently would not gate releases.
8
+
9
+ on:
10
+ workflow_call:
11
+
12
+ # The floor lives on the shared body, not only on `check.yml`: `publish.yml`'s gate
13
+ # calls this same workflow, and if the floor were only on `check.yml` the gate would
14
+ # run this check with the repo-default (possibly write) token. Declared here, the
15
+ # check is read-only from every caller. A reusable workflow can only downscope from
16
+ # the caller, so this never widens anyone's token.
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ check:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 15
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ # Every minor release from the requires-python floor (>=3.11) through the
28
+ # current stable CPython -- the same set the trove classifiers advertise, so
29
+ # each is an audit of the other (no version claimed but untested).
30
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
31
+
32
+ steps:
33
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
34
+ with:
35
+ persist-credentials: false
36
+ - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
37
+
38
+ - name: Install
39
+ # A venv per matrix version, not `uv pip --system`: the runner's system
40
+ # Python is externally managed and is one fixed version whatever the matrix
41
+ # says. This is also what the README tells a person to run.
42
+ run: |
43
+ uv venv --python ${{ matrix.python-version }}
44
+ uv pip install -e ".[dev]"
45
+ .venv/bin/python -c "import sys; print('testing on', sys.version)"
46
+
47
+ - name: Test
48
+ run: .venv/bin/pytest -q
49
+
50
+ - name: Lint
51
+ run: .venv/bin/ruff check src tests
52
+
53
+ - name: Types
54
+ run: .venv/bin/mypy src
55
+
56
+ - name: Confirm the only runtime dependency is httpx
57
+ # A bare install must pull in nothing but httpx; a second runtime dependency
58
+ # creeping into `dependencies` is caught here rather than in a user's
59
+ # environment.
60
+ run: |
61
+ uv venv /tmp/bare --python ${{ matrix.python-version }}
62
+ uv pip install --python /tmp/bare/bin/python .
63
+ /tmp/bare/bin/python -c "
64
+ import re
65
+ import importlib.metadata as md
66
+ requires = md.requires('fisis') or []
67
+ runtime = [r for r in requires if 'extra ==' not in r]
68
+ # Each requirement's bare name (before any version/marker/extra), PEP 503
69
+ # normalized; the runtime set must be exactly httpx.
70
+ names = sorted(re.split(r'[<>=!~;\[\s]', r, maxsplit=1)[0].replace('_', '-').lower() for r in runtime)
71
+ assert names == ['httpx'], f'unexpected runtime dependencies: {runtime}'
72
+ import fisis
73
+ print('fisis imports with only httpx')
74
+ "
75
+ # The console script runs, and its top-level --version prints (not shoved
76
+ # into a required subcommand).
77
+ /tmp/bare/bin/fisis --version
78
+
79
+ - name: Confirm a user's type checker can see the hints
80
+ # Every hint in this package is invisible to a user unless py.typed ships
81
+ # alongside it (PEP 561), and the source cannot answer whether it did:
82
+ # src/fisis/py.typed can sit in git while the built wheel omits it. So ask
83
+ # it the way a user does -- install the built package into a clean
84
+ # environment and run their checker over their code.
85
+ run: |
86
+ uv venv /tmp/typed --python ${{ matrix.python-version }}
87
+ uv pip install --python /tmp/typed/bin/python . mypy
88
+ cat > /tmp/user_code.py <<'PY'
89
+ from fisis import FISIS
90
+
91
+ # list_no takes str; an int is not, so a checker that can see the shipped
92
+ # hints must reject it. If py.typed is missing from the wheel, mypy skips
93
+ # fisis instead and emits no such error.
94
+ FISIS(api_key="k").list_accounts(list_no=123)
95
+ PY
96
+ /tmp/typed/bin/mypy /tmp/user_code.py > /tmp/mypy_out 2>&1 || true
97
+ cat /tmp/mypy_out
98
+ grep -q 'incompatible type' /tmp/mypy_out
fisis-0.1.0/.gitignore ADDED
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ .env
11
+
12
+ # uv
13
+ uv.lock
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .ruff_cache/
18
+ .mypy_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # Local scratch (never tracked)
23
+ dev/
24
+ refs/
25
+
26
+ # AI coding agents
27
+ CLAUDE.md
28
+ .claude/
29
+ AGENTS.md
30
+ AGENT.md
31
+ .codex/
32
+ GEMINI.md
33
+ .gemini/
fisis-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Seokhoon Joo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fisis-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,364 @@
1
+ Metadata-Version: 2.4
2
+ Name: fisis
3
+ Version: 0.1.0
4
+ Summary: Python client for the FISIS (Financial Supervisory Service Financial Statistics Information System) Open API
5
+ Project-URL: Homepage, https://github.com/seokhoonj/fisis
6
+ Project-URL: Source, https://github.com/seokhoonj/fisis
7
+ Project-URL: Issues, https://github.com/seokhoonj/fisis/issues
8
+ Author-email: Seokhoon Joo <seokhoonj@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: data,finance,financial statistics,fisis,fss,korea
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Office/Business :: Financial
23
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.11; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Provides-Extra: pandas
31
+ Requires-Dist: pandas; extra == 'pandas'
32
+ Provides-Extra: polars
33
+ Requires-Dist: polars; extra == 'polars'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # fisis
37
+
38
+ [![check](https://github.com/seokhoonj/fisis/actions/workflows/check.yml/badge.svg)](https://github.com/seokhoonj/fisis/actions/workflows/check.yml)
39
+ [![PyPI](https://img.shields.io/pypi/v/fisis)](https://pypi.org/project/fisis/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/fisis)](https://pypi.org/project/fisis/)
41
+ [![License](https://img.shields.io/pypi/l/fisis)](https://github.com/seokhoonj/fisis/blob/main/LICENSE)
42
+
43
+ [English](https://github.com/seokhoonj/fisis/blob/main/README.en.md) | **한국어**
44
+
45
+ 금융감독원 **금융통계정보시스템(FISIS)** 의 금융회사 감독통계를 읽어옵니다.
46
+
47
+ 은행·생명보험·손해보험·증권·카드·저축은행·여신전문 등 금융권역별 회사 목록과
48
+ 통계표·계정항목, 그리고 분기·반기·연간 시계열 통계자료를 다룹니다. FISIS가 담는 것은
49
+ 금융회사가 감독당국에 제출하는 **업무보고서 기반 감독통계**입니다. 기업 공시서류를 모으는
50
+ **DART(전자공시)** 와 달리, 여기서만 구조화되어 나오는 **감독지표**가 핵심입니다.
51
+
52
+ | 권역 | 대표 감독지표 |
53
+ |---|---|
54
+ | 은행 | BIS 자기자본비율, 고정이하여신비율, 연체율, 예대금리차, ROA·ROE |
55
+ | 증권 | 영업용순자본비율(NCR), 자산건전성, 레버리지 |
56
+ | 카드 | 연체채권비율, 신용·직불·선불 카드이용실적 |
57
+ | 생명보험 | 지급여력비율(RBC/K-ICS), 13·25회 계약유지율, 경영효율지표, 신계약·보유계약·보험료수입 |
58
+ | 손해보험 | 지급여력비율, 계약유지율, 경영효율지표, 보험료수입·보유보험료(장기·자동차·일반) |
59
+
60
+ **DART와 함께** — 재무제표는 DART(전자공시)가 더 충실(주석·XBRL·연결/별도)하고, fisis는 같은
61
+ **감독 별도** 기준으로 재무제표와 감독지표를 한 소스에서 봅니다. 재무제표성 지표만 DART와
62
+ 겹치고(삼성생명 별도 자산총계는 두 소스가 일치), 나머지 감독지표는 fisis 전용입니다. DART 숫자와 교차 검증하려면 [opendart-client](https://github.com/seokhoonj/opendart-client)를 함께 쓰세요.
63
+
64
+ | group | indicator | dart | fisis |
65
+ |---|---|---|---|
66
+ | 공통 | `balance_sheet_assets` · `balance_sheet_liabilities` · `income_statement` | ✅ | ✅ |
67
+ | bank | `capital_adequacy` · `delinquency` · `npl_ratio` · `productivity` | ❌ | ✅ |
68
+ | securities | `net_capital_ratio` · `leverage` | ❌ | ✅ |
69
+ | card | `delinquency` · `credit_card_usage` · `purchase_volume` | ❌ | ✅ |
70
+ | life · nonlife | `solvency` (RBC/K-ICS) · `persistency` · `efficiency` · `premium_income` · `retained_premium` (손보) | ❌ | ✅ |
71
+
72
+ 자주 쓰는 지표는 **접근자**(`fisis.life.company("삼성생명").persistency(start_month="202312",
73
+ end_month="202312")`)로 바로 꺼내고, 그 밖의 통계는 통계표 코드로 조회합니다.
74
+
75
+ ## 1. 설치
76
+
77
+ ```bash
78
+ pip install fisis # 코어
79
+ pip install fisis[pandas] # + Data.to_pandas()
80
+ pip install fisis[polars] # + Data.to_polars()
81
+ ```
82
+
83
+ 이 패키지는 FISIS API 키가 필요합니다. <https://fisis.fss.or.kr/> 의 오픈API 신청에서
84
+ 무료로 발급받으세요(비영리는 즉시 발급). 발급하신 키를 넣는 방법은 다음과 같습니다.
85
+
86
+ **방법 1 — 코드에서 직접 넣기** (바로 한 번 써볼 때)
87
+
88
+ ```python
89
+ from fisis import FISIS
90
+
91
+ fisis = FISIS(api_key="발급받은-키")
92
+ ```
93
+
94
+ **방법 2 — 파일에 저장해서 계속 쓰기** (권장 — 한 번 저장하면 매번 안 넣어도 됩니다)
95
+
96
+ `~/.config/fisis/credentials.json` 파일을 만들고 아래를 넣으세요.
97
+
98
+ ```json
99
+ { "FISIS_API_KEY": "발급받은-키" }
100
+ ```
101
+
102
+ 그러면 이후로는 인자 없이 `FISIS()`만 써도 이 키를 자동으로 찾습니다.
103
+ 탐색 순서는 생성자 인자 → 환경변수 → 파일입니다.
104
+
105
+ > 환경변수를 선호하면, macOS·Linux는 터미널에서 `export FISIS_API_KEY="발급받은-키"`,
106
+ > Windows는 PowerShell에서 `setx FISIS_API_KEY "발급받은-키"`.
107
+
108
+ ## 2. 빠른 시작
109
+
110
+ ```python
111
+ from fisis import FISIS
112
+
113
+ fisis = FISIS() # 저장한 키를 자동으로 찾음
114
+ sl = fisis.life.company("삼성생명") # 회사 손잡이 (코드 "0010595" 도 가능)
115
+ data = sl.persistency(start_month="202312", end_month="202312") # 13·25회 계약유지율
116
+ ```
117
+
118
+ 반환은 `Data` — 값은 `.rows`(`dict`의 목록), 열별 단위는 `.columns`, 결산일은
119
+ `.date_of_settlement`. 행은 표(DataFrame)로 한 줄에 바뀝니다(pandas는 필수가 아닙니다).
120
+
121
+ ```python
122
+ data.rows # [{'base_month': ..., 'account_nm': '13회차 계약유지율', '비율': ...}, ...]
123
+
124
+ # pandas / polars — 설치돼 있으면 변환 헬퍼로 바로
125
+ data.to_pandas()
126
+ data.to_polars()
127
+
128
+ # 또는 직접
129
+ import pandas as pd
130
+ pd.DataFrame(data.rows)
131
+ ```
132
+
133
+ `sector`·`category`·`term`·`lang`은 열거형 멤버, 벤더 코드(`"H"`, `"Q"`), 멤버 이름
134
+ (`"life"`, `"quarterly"`) 어느 쪽으로 넣어도 됩니다.
135
+
136
+ ## 3. 권역 접근자
137
+
138
+ 권역(`fisis.life`, `fisis.bank`, ...)은 명시적으로 정의돼 있어 편집기에서 점(`.`)을 치면
139
+ 자동완성됩니다. 권역에서 회사를 고르고, 회사 손잡이에서 지표를 부릅니다.
140
+
141
+ ```text
142
+ FISIS() # 5개 권역은 이름 붙은 지표, 나머지 17개는 코드로만
143
+ │ 각 권역: .company("<name>" | "<code>") 로 회사(CompanyView)를 얻어 지표를 이름으로 호출
144
+
145
+ ├─ bank
146
+ │ ├─ .capital_adequacy() # 자본적정성 (BIS)
147
+ │ ├─ .delinquency() .npl_ratio() # 연체율 · 고정이하여신
148
+ │ ├─ .deposits() .loans() # 예수금 · 대출금
149
+ │ ├─ .balance_sheet_assets() # 재무상태표 (자산)
150
+ │ ├─ .balance_sheet_liabilities() # 재무상태표 (부채·자본)
151
+ │ ├─ .income_statement() # 손익
152
+ │ └─ ... # 전체 지표는 아래 권역별 표
153
+ ├─ life
154
+ │ ├─ .solvency() # 지급여력 (RBC/K-ICS)
155
+ │ ├─ .persistency() .agent_retention() # 계약유지율 · 설계사정착률
156
+ │ ├─ .new_business() .premium_income() # 신계약 · 보험료수입
157
+ │ ├─ .balance_sheet_assets() # 재무상태표 (자산)
158
+ │ ├─ .balance_sheet_liabilities() # 재무상태표 (부채·자본)
159
+ │ ├─ .income_statement() # 손익
160
+ │ └─ ...
161
+ ├─ nonlife
162
+ │ ├─ .solvency() .persistency() .efficiency() # 지급여력 · 유지율 · 경영효율
163
+ │ ├─ .premium_income() .retained_premium() # 보험료수입 · 보유보험료(장기·자동차·일반)
164
+ │ ├─ .balance_sheet_assets() # 재무상태표 (자산)
165
+ │ ├─ .balance_sheet_liabilities() # 재무상태표 (부채·자본)
166
+ │ ├─ .income_statement() # 손익
167
+ │ └─ ...
168
+ ├─ securities
169
+ │ ├─ .net_capital_ratio() .leverage() # NCR · 레버리지
170
+ │ ├─ .securities_trading() .derivatives_trading() # 증권 · 파생 거래현황
171
+ │ ├─ .balance_sheet_assets() # 재무상태표 (자산)
172
+ │ ├─ .balance_sheet_liabilities() # 재무상태표 (부채·자본)
173
+ │ ├─ .income_statement() # 손익
174
+ │ └─ ...
175
+ ├─ card
176
+ │ ├─ .delinquency() # 연체채권
177
+ │ ├─ .credit_card_usage() .purchase_volume() # 카드이용 · 구매실적
178
+ │ ├─ .balance_sheet_assets() # 재무상태표 (자산)
179
+ │ ├─ .balance_sheet_liabilities() # 재무상태표 (부채·자본)
180
+ │ ├─ .income_statement() # 손익
181
+ │ └─ ...
182
+
183
+ │ * 모든 회사 공통: .fetch(list_no=..., term=..., ...) -> Data(행 + 단위 + 결산일)
184
+
185
+ └─ (+17 sectors) # foreign_bank · savings_bank · capital · futures ...
186
+ └─ .company("<code>").fetch(list_no=...) # 이름 붙은 지표 없이 코드로만
187
+ ```
188
+
189
+ `company(key)`는 `key`가 전부 숫자면 그것을 `finance_cd`로 바로 쓰고(조회 없음), 아니면
190
+ 회사 목록을 받아 `finance_nm`으로 맞춥니다(정확히 일치 우선, 없으면 유일한 부분일치).
191
+ 일치가 없거나 부분일치가 둘 이상이면 후보(이름·코드)를 알려주며 `ValueError`가 납니다.
192
+ 이름 매칭은 요청한 `lang`의 회사 목록을 사용합니다(기본값은 한국어). 영문 회사명은
193
+ `lang="en"`으로 찾거나, 언어와 무관한 숫자 코드를 쓰세요.
194
+
195
+ 각 지표 메서드는 `start_month`·`end_month`(YYYYMM)를 받고, `term`은 그 지표가 실제로
196
+ 받는 값을 기본값으로 둡니다(대부분 분기 `Q`, 유지율은 반기 `H`, 정착률은 연간 `Y`).
197
+ 반환값은 `Data` 하나입니다 — 값은 `.rows`, 열별 **단위**는 `.columns`, **결산일**은
198
+ `.date_of_settlement`에 담겨 옵니다.
199
+
200
+ ### 은행 `fisis.bank`
201
+
202
+ | 메서드 | 지표 | 코드 |
203
+ |---|---|---|
204
+ | `capital_adequacy` | 자본적정성 (BIS) | SA014 |
205
+ | `asset_quality` | 여신건전성 | SA015 |
206
+ | `profitability` | 수익성 (ROA·ROE·예대금리차) | SA017 |
207
+ | `liquidity` | 유동성 | SA018 |
208
+ | `productivity` | 생산성 | SA019 |
209
+ | `delinquency` | 연체율 | SA040 |
210
+ | `npl_ratio` | 고정이하여신 | SA041 |
211
+ | `deposits` `loans` | 예수금·대출금 | SA028 · SA043 |
212
+ | `balance_sheet_assets` `balance_sheet_liabilities` `income_statement` | 재무상태표(자산 / 부채·자본)·손익 | SA003 · SA004 · SA021 |
213
+
214
+ ### 증권 `fisis.securities`
215
+
216
+ | 메서드 | 지표 | 코드 |
217
+ |---|---|---|
218
+ | `net_capital_ratio` | 영업용순자본비율 (NCR) | SF308 |
219
+ | `leverage` | 레버리지 비율 | SF331 |
220
+ | `asset_quality` `liquidity` `profitability` | 자산건전성·유동성·수익성 | SF311 · SF209 · SF210 |
221
+ | `securities_trading` `derivatives_trading` | 증권·파생상품 거래현황 | SF316 · SF317 |
222
+ | `balance_sheet_assets` `balance_sheet_liabilities` `income_statement` | 재무상태표(자산 / 부채·자본)·손익 | SF303 · SF304 · SF307 |
223
+
224
+ ### 신용카드 `fisis.card`
225
+
226
+ | 메서드 | 지표 | 코드 |
227
+ |---|---|---|
228
+ | `capital_adequacy` `asset_quality` `profitability` `liquidity` | 자본적정성·여신건전성·수익성·유동성 | SC007 · SC008 · SC009 · SC010 |
229
+ | `delinquency` | 연체채권비율 | SC117 |
230
+ | `credit_card_usage` `debit_card_usage` `purchase_volume` | 신용·직불 카드이용실적·구매실적 | SC013 · SC014 · SC016 |
231
+ | `balance_sheet_assets` `balance_sheet_liabilities` `income_statement` | 재무상태표(자산 / 부채·자본)·손익 | SC103 · SC104 · SC218 |
232
+
233
+ ### 생명보험 `fisis.life` / 손해보험 `fisis.nonlife`
234
+
235
+ | 메서드 | 지표 | 코드(생·손보) |
236
+ |---|---|---|
237
+ | `solvency` | 지급여력비율 (RBC/K-ICS) | SH021 / SI021 |
238
+ | `efficiency` | 경영효율지표 | SH114 / SI114 |
239
+ | `persistency` | 계약유지율 (13·25회) | SH025 / SI025 |
240
+ | `agent_retention` | 설계사정착률 | SH022 / SI022 |
241
+ | `asset_quality` | 자산건전성 | SH112 / SI112 |
242
+ | `liquidity` | 유동성 | SH115 / SI115 |
243
+ | `balance_sheet_assets` | 요약재무상태표 (자산) | SH150 / SI146 |
244
+ | `balance_sheet_liabilities` | 요약재무상태표 (부채·자본) | SH151 / SI147 |
245
+ | `income_statement` | 요약손익계산서 | SH154 / SI150 |
246
+ | `new_business` `in_force` `premium_income` | 신계약·보유계약·보험료수입 | SH160 · SH161 · SH166 (생보) |
247
+ | `premium_income` `retained_premium` | 보험료수입(수납형태)·보유보험료(장기·자동차·일반) | SI027 · SI138 (손보) |
248
+
249
+ 권역 속성 — **이름 붙은 지표가 있는 5개**: `bank` `life` `nonlife` `securities` `card`.
250
+ **코드로만 조회하는 17개**: `foreign_bank` `futures` `asset_management` `investment_advisory`
251
+ `merchant_bank` `leasing` `capital` `new_tech` `savings_bank` `credit_union` `nonghyup`
252
+ `suhyup` `forestry_coop` `real_estate_trust` `holding` `trust_common` `derivatives_common`.
253
+
254
+ ## 4. 평면 메서드 — 탐색 흐름
255
+
256
+ 이름 붙은 지표가 없는 통계표나 다른 권역은, 코드를 한 단계씩 찾아 내려갑니다. FISIS는
257
+ 시계열을 **회사(`finance_cd`) + 통계표(`list_no`) + 계정항목(`account_cd`)** 으로
258
+ 식별합니다.
259
+
260
+ ```python
261
+ from fisis import FISIS, Sector, Term
262
+
263
+ fisis = FISIS()
264
+
265
+ companies = fisis.list_companies(sector=Sector.LIFE) # 회사 -> finance_cd
266
+ statistics = fisis.list_statistics(sector=Sector.LIFE) # 통계표 -> list_no
267
+ accounts = fisis.list_accounts(list_no=statistics[0]["list_no"]) # 계정항목 -> account_cd
268
+
269
+ data = fisis.fetch_data( # 통계자료 (YYYYMM, 최대 40분기)
270
+ finance_cd=companies[0]["finance_cd"],
271
+ list_no=statistics[0]["list_no"],
272
+ term=Term.QUARTERLY, start_month="202403", end_month="202412",
273
+ )
274
+ ```
275
+
276
+ FISIS 원본 응답의 값 열은 `a`·`b`·`c`·`d` 같은 무의미한 이름이지만, `fetch_data`가 응답의
277
+ 컬럼 설명으로 자동 해석해 사람이 읽는 이름(예: `말잔`·`평잔`)으로 돌려줍니다. 반환값은
278
+ 행에 더해 열별 이름·단위(`Column`)와 결산일을 담은 `Data` 하나입니다.
279
+
280
+ ```python
281
+ data.rows # [{'base_month': '202403', '말잔': ...}, ...]
282
+ [(c.name, c.unit) for c in data.columns] # 예: [('금액', '원'), ('구성비', '%')]
283
+ data.date_of_settlement # 예: '12/31'
284
+
285
+ # 행은 pandas·polars가 바로 먹는 records 형식입니다(값은 FISIS가 주는 대로 문자열 —
286
+ # 필요한 열만 캐스팅하세요). 변환 헬퍼는 해당 라이브러리가 설치돼 있을 때 씁니다:
287
+ data.to_polars() # polars.DataFrame
288
+ data.to_pandas() # pandas.DataFrame
289
+ ```
290
+
291
+ ## 5. 커맨드라인
292
+
293
+ 설치하면 `fisis` 명령이 함께 깔립니다.
294
+
295
+ ```sh
296
+ fisis companies --sector life # 권역의 회사 목록
297
+ fisis statistics --sector life --category key_metrics # 통계표 목록
298
+ fisis accounts SH025 # 통계표의 계정항목
299
+ fisis data 0010595 SH025 --term H --start 202312 --end 202312 # 통계자료 (유지율)
300
+ fisis data 0010595 SH150 --term Q --start 202403 --end 202403 --table # + 열별 단위·결산일
301
+ ```
302
+
303
+ `--json`으로 전체 결과를, `fisis <명령> --help`로 옵션을 봅니다.
304
+
305
+ ## 6. AI 코딩 에이전트에서 사용
306
+
307
+ - 이 저장소는 Claude Code·Codex용 플러그인 마켓플레이스도 겸합니다.
308
+ - `companies`·`statistics`·`accounts`·`data` 스킬을 제공하며, 각각 같은 이름의 `fisis` 명령에
309
+ 대응합니다.
310
+ - 먼저 패키지를 설치하고 API 키를 설정하세요.
311
+
312
+ ### 6.1 Claude Code
313
+
314
+ Claude Code 채팅창에서 마켓플레이스를 추가하고 설치합니다:
315
+
316
+ ```
317
+ /plugin marketplace add seokhoonj/fisis
318
+ /plugin install fisis@fisis
319
+ ```
320
+
321
+ 설치 후 평범하게 물어보거나("생명보험사 목록 보여줘", "삼성생명 유지율 가져와"), 스킬을 직접
322
+ 부르세요 — `/fisis:companies --sector life`, `/fisis:data 0010595 SH025 --term H ...`.
323
+
324
+ ### 6.2 Codex
325
+
326
+ 터미널에서 마켓플레이스를 추가하고 설치합니다:
327
+
328
+ ```
329
+ codex plugin marketplace add seokhoonj/fisis
330
+ codex plugin add fisis@fisis
331
+ ```
332
+
333
+ ### 6.3 플러그인 없이 (symlink)
334
+
335
+ 플러그인으로 설치하지 않고 쓰려면, 스킬을 각 에이전트의 스킬 디렉터리에 symlink합니다.
336
+
337
+ ```sh
338
+ ln -s "$PWD/plugins/fisis/skills/data" ~/.claude/skills/data # Claude Code → /data
339
+ ln -s "$PWD/plugins/fisis/skills/data" ~/.codex/skills/data # Codex → $fisis:data
340
+ ```
341
+
342
+ Claude Code는 바로 인식하고, Codex는 재시작해야 로딩됩니다.
343
+
344
+ ## 7. 에러
345
+
346
+ | 예외 | 언제 |
347
+ |---|---|
348
+ | `FISISConfigError` | API 키를 찾지 못했을 때 |
349
+ | `FISISAuthError` | FISIS가 키를 거부했을 때 (미등록·중지·삭제·샘플 키) |
350
+ | `FISISRateLimitError` | 일일검색 허용횟수 초과(err 020) 또는 HTTP 429 |
351
+ | `FISISResponseError` | FISIS가 에러를 돌려줬을 때 (`.code`·`.message`, 예: 40분기 초과 = 103) |
352
+ | `FISISNetworkError` | 네트워크가 끝내 안 됐을 때 |
353
+
354
+ - 모든 예외는 `FISISError`의 하위입니다.
355
+ - 조회 결과가 없으면 에러가 아니라 빈 결과로 옵니다 (카탈로그 조회는 빈 목록, `fetch_data`·지표 메서드는 행이 빈 `Data`).
356
+ - 여러 회사·통계표를 잇달아 읽을 때는 `FISIS(delay_seconds=0.3)`으로 간격을 둡니다.
357
+ - 에러 메시지와 표현에는 API 키가 절대 담기지 않습니다.
358
+
359
+ ## 8. 라이선스
360
+
361
+ 코드: MIT © Seokhoon Joo.
362
+
363
+ 데이터: FISIS 통계정보의 출처는 금융감독원 금융통계정보시스템이며, 국가승인통계가 아닌
364
+ 업무보고서 기반 자료입니다. 데이터 이용 시 FISIS 이용약관과 출처 표기를 따르세요.