finlab-sentinel 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 (49) hide show
  1. finlab_sentinel-0.1.0/.github/workflows/ci.yml +77 -0
  2. finlab_sentinel-0.1.0/.github/workflows/release.yml +47 -0
  3. finlab_sentinel-0.1.0/.gitignore +146 -0
  4. finlab_sentinel-0.1.0/PKG-INFO +198 -0
  5. finlab_sentinel-0.1.0/README.md +159 -0
  6. finlab_sentinel-0.1.0/pyproject.toml +118 -0
  7. finlab_sentinel-0.1.0/sentinel.toml.example +70 -0
  8. finlab_sentinel-0.1.0/src/finlab_sentinel/__init__.py +15 -0
  9. finlab_sentinel-0.1.0/src/finlab_sentinel/cli/__init__.py +5 -0
  10. finlab_sentinel-0.1.0/src/finlab_sentinel/cli/main.py +485 -0
  11. finlab_sentinel-0.1.0/src/finlab_sentinel/comparison/__init__.py +27 -0
  12. finlab_sentinel-0.1.0/src/finlab_sentinel/comparison/differ.py +335 -0
  13. finlab_sentinel-0.1.0/src/finlab_sentinel/comparison/hasher.py +172 -0
  14. finlab_sentinel-0.1.0/src/finlab_sentinel/comparison/policies.py +207 -0
  15. finlab_sentinel-0.1.0/src/finlab_sentinel/comparison/report.py +259 -0
  16. finlab_sentinel-0.1.0/src/finlab_sentinel/config/__init__.py +25 -0
  17. finlab_sentinel-0.1.0/src/finlab_sentinel/config/loader.py +222 -0
  18. finlab_sentinel-0.1.0/src/finlab_sentinel/config/schema.py +128 -0
  19. finlab_sentinel-0.1.0/src/finlab_sentinel/core/__init__.py +9 -0
  20. finlab_sentinel-0.1.0/src/finlab_sentinel/core/interceptor.py +275 -0
  21. finlab_sentinel-0.1.0/src/finlab_sentinel/core/patcher.py +151 -0
  22. finlab_sentinel-0.1.0/src/finlab_sentinel/core/registry.py +69 -0
  23. finlab_sentinel-0.1.0/src/finlab_sentinel/exceptions.py +43 -0
  24. finlab_sentinel-0.1.0/src/finlab_sentinel/handlers/__init__.py +17 -0
  25. finlab_sentinel-0.1.0/src/finlab_sentinel/handlers/base.py +43 -0
  26. finlab_sentinel-0.1.0/src/finlab_sentinel/handlers/callback.py +110 -0
  27. finlab_sentinel-0.1.0/src/finlab_sentinel/handlers/exception.py +39 -0
  28. finlab_sentinel-0.1.0/src/finlab_sentinel/handlers/warning.py +92 -0
  29. finlab_sentinel-0.1.0/src/finlab_sentinel/py.typed +0 -0
  30. finlab_sentinel-0.1.0/src/finlab_sentinel/storage/__init__.py +10 -0
  31. finlab_sentinel-0.1.0/src/finlab_sentinel/storage/backend.py +186 -0
  32. finlab_sentinel-0.1.0/src/finlab_sentinel/storage/cleanup.py +80 -0
  33. finlab_sentinel-0.1.0/src/finlab_sentinel/storage/index.py +296 -0
  34. finlab_sentinel-0.1.0/src/finlab_sentinel/storage/parquet.py +307 -0
  35. finlab_sentinel-0.1.0/tests/__init__.py +1 -0
  36. finlab_sentinel-0.1.0/tests/conftest.py +98 -0
  37. finlab_sentinel-0.1.0/tests/unit/__init__.py +1 -0
  38. finlab_sentinel-0.1.0/tests/unit/test_cleanup.py +100 -0
  39. finlab_sentinel-0.1.0/tests/unit/test_cli.py +382 -0
  40. finlab_sentinel-0.1.0/tests/unit/test_config.py +158 -0
  41. finlab_sentinel-0.1.0/tests/unit/test_differ.py +154 -0
  42. finlab_sentinel-0.1.0/tests/unit/test_handlers.py +173 -0
  43. finlab_sentinel-0.1.0/tests/unit/test_hasher.py +100 -0
  44. finlab_sentinel-0.1.0/tests/unit/test_interceptor.py +332 -0
  45. finlab_sentinel-0.1.0/tests/unit/test_loader.py +202 -0
  46. finlab_sentinel-0.1.0/tests/unit/test_patcher.py +199 -0
  47. finlab_sentinel-0.1.0/tests/unit/test_policies.py +185 -0
  48. finlab_sentinel-0.1.0/tests/unit/test_report.py +227 -0
  49. finlab_sentinel-0.1.0/tests/unit/test_storage.py +164 -0
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up uv
20
+ uses: astral-sh/setup-uv@v3
21
+ with:
22
+ version: "latest"
23
+
24
+ - name: Set up Python
25
+ run: uv python install 3.12
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --extra dev
29
+
30
+ - name: Run ruff linter
31
+ run: uv run ruff check src/ tests/
32
+
33
+ - name: Run ruff formatter check
34
+ run: uv run ruff format --check src/ tests/
35
+
36
+ - name: Run mypy type checker
37
+ run: uv run mypy src/
38
+
39
+ test:
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ os: [ubuntu-latest, macos-latest, windows-latest]
45
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
46
+
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+
50
+ - name: Set up uv
51
+ uses: astral-sh/setup-uv@v3
52
+ with:
53
+ version: "latest"
54
+
55
+ - name: Set up Python ${{ matrix.python-version }}
56
+ run: uv python install ${{ matrix.python-version }}
57
+
58
+ - name: Install dependencies
59
+ run: uv sync --extra dev
60
+
61
+ - name: Run tests with coverage
62
+ run: uv run pytest tests/ -v --cov=src/finlab_sentinel --cov-report=xml --cov-report=html --cov-fail-under=80 --junitxml=junit/test-results-${{ matrix.python-version }}.xml
63
+
64
+ - name: Upload coverage reports to Codecov
65
+ if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
66
+ uses: codecov/codecov-action@v5
67
+ with:
68
+ token: ${{ secrets.CODECOV_TOKEN }}
69
+
70
+ - name: Upload test results
71
+ uses: actions/upload-artifact@v4
72
+ if: always()
73
+ with:
74
+ name: test-results-${{ matrix.os }}-${{ matrix.python-version }}
75
+ path: |
76
+ junit/
77
+ htmlcov/
@@ -0,0 +1,47 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch: # Manual trigger only
5
+
6
+ jobs:
7
+ publish:
8
+ name: Build and Publish to PyPI
9
+ runs-on: ubuntu-latest
10
+ environment: pypi-publish # Requires environment approval
11
+ steps:
12
+ - name: Check out code
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v3
17
+ with:
18
+ version: "latest"
19
+
20
+ - name: Set up Python
21
+ run: uv python install 3.12
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --extra dev
25
+
26
+ - name: Verify project configuration
27
+ run: |
28
+ uv run python -c "import sys; sys.path.insert(0, 'src'); import finlab_sentinel; print(f'Package version: {finlab_sentinel.__version__}')"
29
+
30
+ - name: Build package
31
+ run: uv build
32
+
33
+ - name: Check package contents
34
+ run: uv run twine check dist/*
35
+
36
+ - name: Publish to PyPI
37
+ uses: pypa/gh-action-pypi-publish@release/v1
38
+ with:
39
+ password: ${{ secrets.PYPI_API_TOKEN }}
40
+ verbose: true
41
+
42
+ - name: Upload build artifacts
43
+ uses: actions/upload-artifact@v4
44
+ if: always()
45
+ with:
46
+ name: dist
47
+ path: dist/
@@ -0,0 +1,146 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ *.py,cover
48
+ .hypothesis/
49
+ .pytest_cache/
50
+ junit/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ .pybuilder/
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ Pipfile.lock
88
+
89
+ # PEP 582
90
+ __pypackages__/
91
+
92
+ # Celery stuff
93
+ celerybeat-schedule
94
+ celerybeat.pid
95
+
96
+ # SageMath parsed files
97
+ *.sage.py
98
+
99
+ # Environments
100
+ .env
101
+ .venv
102
+ env/
103
+ venv/
104
+ ENV/
105
+ env.bak/
106
+ venv.bak/
107
+
108
+ # Spyder project settings
109
+ .spyderproject
110
+ .spyproject
111
+
112
+ # Rope project settings
113
+ .ropeproject
114
+
115
+ # mkdocs documentation
116
+ /site
117
+
118
+ # mypy
119
+ .mypy_cache/
120
+ .dmypy.json
121
+ dmypy.json
122
+
123
+ # Pyre type checker
124
+ .pyre/
125
+
126
+ # pytype static type analyzer
127
+ .pytype/
128
+
129
+ # Cython debug symbols
130
+ cython_debug/
131
+
132
+ # IDE
133
+ .idea/
134
+ .vscode/
135
+ *.swp
136
+ *.swo
137
+ *~
138
+
139
+ # Local config
140
+ sentinel.toml
141
+
142
+ # uv
143
+ uv.lock
144
+
145
+ # finlab-sentinel specific
146
+ .finlab-sentinel/
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: finlab-sentinel
3
+ Version: 0.1.0
4
+ Summary: Defensive monitoring layer for finlab data.get API - detect unexpected data changes
5
+ Project-URL: Homepage, https://github.com/iapcal/finlab-sentinel
6
+ Project-URL: Repository, https://github.com/iapcal/finlab-sentinel
7
+ Project-URL: Issues, https://github.com/iapcal/finlab-sentinel/issues
8
+ Author-email: iapcal <chiyimin2018@gmail.com>
9
+ License: MIT
10
+ Keywords: backtesting,data-validation,finlab,monitoring,taiwan-stock
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Office/Business :: Financial :: Investment
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: pandas>=2.0.0
24
+ Requires-Dist: pyarrow>=14.0.0
25
+ Requires-Dist: pydantic-settings>=2.0.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: rich>=13.0.0
28
+ Requires-Dist: typer>=0.12.0
29
+ Requires-Dist: xxhash>=3.0.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
32
+ Requires-Dist: pandas-stubs>=2.0.0; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
34
+ Requires-Dist: pytest-mock>=3.0.0; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
37
+ Requires-Dist: twine>=5.0.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # finlab-sentinel
41
+
42
+ ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)
43
+ ![Windows](https://img.shields.io/badge/OS-Windows-0078D6?logo=windows&logoColor=white)
44
+ ![Linux](https://img.shields.io/badge/OS-Linux-FCC624?logo=linux&logoColor=black)
45
+ ![macOS](https://img.shields.io/badge/OS-macOS-000000?logo=apple&logoColor=white)
46
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
47
+ [![CI](https://github.com/iapcal/finlab-sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/iapcal/finlab-sentinel/actions/workflows/ci.yml)
48
+ [![coverage](https://img.shields.io/codecov/c/github/iapcal/finlab-sentinel)](https://codecov.io/gh/iapcal/finlab-sentinel)
49
+
50
+ **finlab-sentinel** 是 [finlab](https://github.com/finlab-python/finlab) 套件的防禦層,用於監控 `data.get` API 的資料變化,防止未預期的資料異動影響回測或選股結果。
51
+
52
+ ## 功能特色
53
+
54
+ - **自動比對**: 每次 `data.get` 時自動比對歷史資料
55
+ - **滾動備份**: 保留 7 天(可配置)的備份資料
56
+ - **智慧檢測**:
57
+ - 數值容差比對(可配置 rtol/atol)
58
+ - dtype 變更檢測
59
+ - NA 類型差異檢測(pd.NA vs np.nan vs None)
60
+ - **彈性政策**:
61
+ - `append_only`: 只允許新增,不允許刪除或修改歷史
62
+ - `threshold`: 允許小幅度變更(如 10% 以內)
63
+ - 黑名單配置:指定可修改歷史的資料集
64
+ - **可配置行為**:
65
+ - 拋出例外(預設)
66
+ - 警告並使用快取
67
+ - 警告並使用新資料
68
+ - **通知機制**: 支援自訂 callback(如 LINE、email 通知)
69
+ - **CLI 工具**: 管理備份、查看差異、接受新資料
70
+
71
+ ## 安裝
72
+
73
+ ```bash
74
+ pip install finlab-sentinel
75
+ ```
76
+
77
+ 或使用 uv:
78
+
79
+ ```bash
80
+ uv add finlab-sentinel
81
+ ```
82
+
83
+ ## 快速開始
84
+
85
+ ```python
86
+ import finlab_sentinel
87
+
88
+ # 啟用 sentinel
89
+ finlab_sentinel.enable()
90
+
91
+ # 正常使用 finlab
92
+ from finlab import data
93
+ close = data.get('price:收盤價') # 自動備份並比對
94
+
95
+ # 如果資料異常,會根據配置拋出例外或警告
96
+ ```
97
+
98
+ ## 配置
99
+
100
+ 建立 `sentinel.toml` 檔案:
101
+
102
+ ```toml
103
+ [storage]
104
+ path = "~/.finlab-sentinel/"
105
+ retention_days = 7
106
+
107
+ [comparison]
108
+ rtol = 1e-5
109
+ change_threshold = 0.10
110
+
111
+ [comparison.policies]
112
+ default_mode = "append_only"
113
+ history_modifiable = ["fundamental_features:某些財報資料"]
114
+
115
+ [anomaly]
116
+ behavior = "raise" # raise | warn_return_cached | warn_return_new
117
+ save_reports = true
118
+
119
+ # 可選:設定通知 callback
120
+ # callback = "myproject.notifications:send_line"
121
+ ```
122
+
123
+ ## CLI 使用
124
+
125
+ ```bash
126
+ # 列出所有備份
127
+ sentinel list
128
+
129
+ # 清理過期備份
130
+ sentinel cleanup --days 14
131
+
132
+ # 查看資料差異
133
+ sentinel diff "price:收盤價"
134
+
135
+ # 接受新資料作為基準
136
+ sentinel accept "price:收盤價" --reason "確認資料修正"
137
+
138
+ # 匯出備份
139
+ sentinel export "price:收盤價" -o ./backup.parquet
140
+ ```
141
+
142
+ ## 處理資料異常
143
+
144
+ 當檢測到資料異常時:
145
+
146
+ ```python
147
+ from finlab_sentinel import DataAnomalyError
148
+
149
+ try:
150
+ close = data.get('price:收盤價')
151
+ except DataAnomalyError as e:
152
+ print(f"資料異常: {e.report.summary}")
153
+ # 檢查報告詳情
154
+ print(f"變動比例: {e.report.comparison_result.change_ratio:.1%}")
155
+
156
+ # 如果確認要接受新資料
157
+ from finlab_sentinel.core.interceptor import accept_current_data
158
+ accept_current_data('price:收盤價', reason="確認資料修正")
159
+ ```
160
+
161
+ ## 自訂通知
162
+
163
+ ```python
164
+ def send_line_notification(report):
165
+ """當檢測到異常時發送 LINE 通知"""
166
+ import requests
167
+ requests.post(
168
+ "https://notify-api.line.me/api/notify",
169
+ headers={"Authorization": f"Bearer {LINE_TOKEN}"},
170
+ data={"message": f"finlab 資料異常: {report.summary}"}
171
+ )
172
+
173
+ # 在 sentinel.toml 中設定
174
+ # [anomaly]
175
+ # callback = "myproject.notifications:send_line_notification"
176
+ ```
177
+
178
+ ## 開發
179
+
180
+ ```bash
181
+ # Clone 專案
182
+ git clone https://github.com/yourusername/finlab-sentinel
183
+ cd finlab-sentinel
184
+
185
+ # 使用 uv 安裝開發依賴
186
+ uv sync --dev
187
+
188
+ # 執行測試
189
+ uv run pytest
190
+
191
+ # 執行 lint
192
+ uv run ruff check src/ tests/
193
+ uv run mypy src/
194
+ ```
195
+
196
+ ## License
197
+
198
+ MIT License
@@ -0,0 +1,159 @@
1
+ # finlab-sentinel
2
+
3
+ ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)
4
+ ![Windows](https://img.shields.io/badge/OS-Windows-0078D6?logo=windows&logoColor=white)
5
+ ![Linux](https://img.shields.io/badge/OS-Linux-FCC624?logo=linux&logoColor=black)
6
+ ![macOS](https://img.shields.io/badge/OS-macOS-000000?logo=apple&logoColor=white)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![CI](https://github.com/iapcal/finlab-sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/iapcal/finlab-sentinel/actions/workflows/ci.yml)
9
+ [![coverage](https://img.shields.io/codecov/c/github/iapcal/finlab-sentinel)](https://codecov.io/gh/iapcal/finlab-sentinel)
10
+
11
+ **finlab-sentinel** 是 [finlab](https://github.com/finlab-python/finlab) 套件的防禦層,用於監控 `data.get` API 的資料變化,防止未預期的資料異動影響回測或選股結果。
12
+
13
+ ## 功能特色
14
+
15
+ - **自動比對**: 每次 `data.get` 時自動比對歷史資料
16
+ - **滾動備份**: 保留 7 天(可配置)的備份資料
17
+ - **智慧檢測**:
18
+ - 數值容差比對(可配置 rtol/atol)
19
+ - dtype 變更檢測
20
+ - NA 類型差異檢測(pd.NA vs np.nan vs None)
21
+ - **彈性政策**:
22
+ - `append_only`: 只允許新增,不允許刪除或修改歷史
23
+ - `threshold`: 允許小幅度變更(如 10% 以內)
24
+ - 黑名單配置:指定可修改歷史的資料集
25
+ - **可配置行為**:
26
+ - 拋出例外(預設)
27
+ - 警告並使用快取
28
+ - 警告並使用新資料
29
+ - **通知機制**: 支援自訂 callback(如 LINE、email 通知)
30
+ - **CLI 工具**: 管理備份、查看差異、接受新資料
31
+
32
+ ## 安裝
33
+
34
+ ```bash
35
+ pip install finlab-sentinel
36
+ ```
37
+
38
+ 或使用 uv:
39
+
40
+ ```bash
41
+ uv add finlab-sentinel
42
+ ```
43
+
44
+ ## 快速開始
45
+
46
+ ```python
47
+ import finlab_sentinel
48
+
49
+ # 啟用 sentinel
50
+ finlab_sentinel.enable()
51
+
52
+ # 正常使用 finlab
53
+ from finlab import data
54
+ close = data.get('price:收盤價') # 自動備份並比對
55
+
56
+ # 如果資料異常,會根據配置拋出例外或警告
57
+ ```
58
+
59
+ ## 配置
60
+
61
+ 建立 `sentinel.toml` 檔案:
62
+
63
+ ```toml
64
+ [storage]
65
+ path = "~/.finlab-sentinel/"
66
+ retention_days = 7
67
+
68
+ [comparison]
69
+ rtol = 1e-5
70
+ change_threshold = 0.10
71
+
72
+ [comparison.policies]
73
+ default_mode = "append_only"
74
+ history_modifiable = ["fundamental_features:某些財報資料"]
75
+
76
+ [anomaly]
77
+ behavior = "raise" # raise | warn_return_cached | warn_return_new
78
+ save_reports = true
79
+
80
+ # 可選:設定通知 callback
81
+ # callback = "myproject.notifications:send_line"
82
+ ```
83
+
84
+ ## CLI 使用
85
+
86
+ ```bash
87
+ # 列出所有備份
88
+ sentinel list
89
+
90
+ # 清理過期備份
91
+ sentinel cleanup --days 14
92
+
93
+ # 查看資料差異
94
+ sentinel diff "price:收盤價"
95
+
96
+ # 接受新資料作為基準
97
+ sentinel accept "price:收盤價" --reason "確認資料修正"
98
+
99
+ # 匯出備份
100
+ sentinel export "price:收盤價" -o ./backup.parquet
101
+ ```
102
+
103
+ ## 處理資料異常
104
+
105
+ 當檢測到資料異常時:
106
+
107
+ ```python
108
+ from finlab_sentinel import DataAnomalyError
109
+
110
+ try:
111
+ close = data.get('price:收盤價')
112
+ except DataAnomalyError as e:
113
+ print(f"資料異常: {e.report.summary}")
114
+ # 檢查報告詳情
115
+ print(f"變動比例: {e.report.comparison_result.change_ratio:.1%}")
116
+
117
+ # 如果確認要接受新資料
118
+ from finlab_sentinel.core.interceptor import accept_current_data
119
+ accept_current_data('price:收盤價', reason="確認資料修正")
120
+ ```
121
+
122
+ ## 自訂通知
123
+
124
+ ```python
125
+ def send_line_notification(report):
126
+ """當檢測到異常時發送 LINE 通知"""
127
+ import requests
128
+ requests.post(
129
+ "https://notify-api.line.me/api/notify",
130
+ headers={"Authorization": f"Bearer {LINE_TOKEN}"},
131
+ data={"message": f"finlab 資料異常: {report.summary}"}
132
+ )
133
+
134
+ # 在 sentinel.toml 中設定
135
+ # [anomaly]
136
+ # callback = "myproject.notifications:send_line_notification"
137
+ ```
138
+
139
+ ## 開發
140
+
141
+ ```bash
142
+ # Clone 專案
143
+ git clone https://github.com/yourusername/finlab-sentinel
144
+ cd finlab-sentinel
145
+
146
+ # 使用 uv 安裝開發依賴
147
+ uv sync --dev
148
+
149
+ # 執行測試
150
+ uv run pytest
151
+
152
+ # 執行 lint
153
+ uv run ruff check src/ tests/
154
+ uv run mypy src/
155
+ ```
156
+
157
+ ## License
158
+
159
+ MIT License