programgarden-community 2.0.1__tar.gz → 2.2.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.
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/PKG-INFO +4 -5
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/README.md +2 -3
- programgarden_community-2.2.0/programgarden_community/nodes/__init__.py +18 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/nodes_registry.py +0 -46
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/pyproject.toml +2 -2
- programgarden_community-2.0.1/programgarden_community/nodes/__init__.py +0 -23
- programgarden_community-2.0.1/programgarden_community/nodes/data/__init__.py +0 -14
- programgarden_community-2.0.1/programgarden_community/nodes/data/_parsers.py +0 -343
- programgarden_community-2.0.1/programgarden_community/nodes/data/file_reader.py +0 -554
- programgarden_community-2.0.1/programgarden_community/nodes/market/__init__.py +0 -11
- programgarden_community-2.0.1/programgarden_community/nodes/market/fear_greed.py +0 -340
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/nodes/analysis/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/nodes/analysis/performance_report.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/nodes/messaging/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/nodes/messaging/telegram.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/_position_qty.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/_ta_common.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/adx/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/aroon/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/atr/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/beta_hedge/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/bollinger_bands/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/bollinger_bands/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/breakout_retest/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/calendar_spread/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/calmar_ratio/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/cci/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/cmf/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/cmo/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/connors_rsi/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/contango_backwardation/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/coppock_curve/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/correlation_analysis/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/correlation_guard/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/dcf_fair_value/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/doji/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/drawdown_protection/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/dual_momentum/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/dual_momentum/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/dynamic_stop_loss/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/elder_ray/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/engulfing/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/ev_ebitda_screen/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/fisher_transform/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/golden_ratio/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/graham_number/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/hammer_shooting_star/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/heikin_ashi/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/hurst_exponent/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/ichimoku_cloud/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/kdj/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/kelly_criterion/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/keltner_channel/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/level_touch/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/ma_cross/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/ma_cross/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/macd/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/macd/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/magic_formula/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/market_internals/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/max_position_limit/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/mean_reversion/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/mfi/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/momentum_rank/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/morning_evening_star/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/multi_timeframe_confirmation/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/obv/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/pair_trading/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/parabolic_sar/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/partial_take_profit/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/piotroski_f_score/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/pivot_point/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/portfolio_optimizer/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/price_channel/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/profit_target/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/profit_target/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/qqe/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/regime_detection/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/relative_strength/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/risk_parity/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/roll_management/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/rsi/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/rsi/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/rsi_divergence/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/schaff_trend_cycle/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/seasonal_filter/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/sharpe_ratio_monitor/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/sortino_ratio/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/squeeze_momentum/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/stochastic/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/stop_loss/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/stop_loss/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/supertrend/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/support_resistance_levels/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/tactical_asset_allocation/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/three_line_strike/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/time_based_exit/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/time_series_momentum/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/trailing_stop/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/trailing_stop/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/trix/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/turtle_breakout/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/ultimate_oscillator/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/var_cvar_monitor/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/volatility_breakout/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/volatility_position_sizing/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/volume_spike/README.md +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/volume_spike/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/vortex_indicator/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/vwap/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/williams_r/__init__.py +0 -0
- {programgarden_community-2.0.1 → programgarden_community-2.2.0}/programgarden_community/plugins/z_score/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: programgarden-community
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: ProgramGarden Community - 전략 플러그인 모음
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Author: 프로그램동산
|
|
@@ -18,7 +18,7 @@ Provides-Extra: portfolio
|
|
|
18
18
|
Provides-Extra: quant-all
|
|
19
19
|
Provides-Extra: xlsx
|
|
20
20
|
Requires-Dist: packaging (>=23) ; extra == "portfolio" or extra == "quant-all"
|
|
21
|
-
Requires-Dist: programgarden-core (>=2.0
|
|
21
|
+
Requires-Dist: programgarden-core (>=2.3.0,<3.0.0)
|
|
22
22
|
Requires-Dist: pypdf (>=4.0.0)
|
|
23
23
|
Requires-Dist: pyportfolioopt (>=1.6,<2) ; (python_version < "3.15") and (extra == "portfolio" or extra == "quant-all")
|
|
24
24
|
Requires-Dist: quantstats (>=0.0.81,<0.1) ; extra == "perf" or extra == "quant-all"
|
|
@@ -119,13 +119,12 @@ poetry add programgarden-community
|
|
|
119
119
|
| DynamicStopLoss | ATR 기반 동적 손절 |
|
|
120
120
|
| LevelTouch | 레벨 터치/돌파/역할전환 감지 |
|
|
121
121
|
|
|
122
|
-
### 커뮤니티 노드 (
|
|
122
|
+
### 커뮤니티 노드 (2개)
|
|
123
123
|
|
|
124
124
|
| 노드 | 카테고리 | 설명 |
|
|
125
125
|
|------|----------|------|
|
|
126
126
|
| TelegramNode | messaging | Telegram Bot API 메시지 전송 |
|
|
127
|
-
|
|
|
128
|
-
| FileReaderNode | data | 파일 파싱 (PDF, TXT, CSV, JSON, MD, DOCX, XLSX) |
|
|
127
|
+
| PerformanceReportNode | analysis | 성과/리스크 리포트 (Sharpe, Sortino, drawdown, CAGR, beta) |
|
|
129
128
|
|
|
130
129
|
## 사용법
|
|
131
130
|
|
|
@@ -92,13 +92,12 @@ poetry add programgarden-community
|
|
|
92
92
|
| DynamicStopLoss | ATR 기반 동적 손절 |
|
|
93
93
|
| LevelTouch | 레벨 터치/돌파/역할전환 감지 |
|
|
94
94
|
|
|
95
|
-
### 커뮤니티 노드 (
|
|
95
|
+
### 커뮤니티 노드 (2개)
|
|
96
96
|
|
|
97
97
|
| 노드 | 카테고리 | 설명 |
|
|
98
98
|
|------|----------|------|
|
|
99
99
|
| TelegramNode | messaging | Telegram Bot API 메시지 전송 |
|
|
100
|
-
|
|
|
101
|
-
| FileReaderNode | data | 파일 파싱 (PDF, TXT, CSV, JSON, MD, DOCX, XLSX) |
|
|
100
|
+
| PerformanceReportNode | analysis | 성과/리스크 리포트 (Sharpe, Sortino, drawdown, CAGR, beta) |
|
|
102
101
|
|
|
103
102
|
## 사용법
|
|
104
103
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ProgramGarden Community - 커뮤니티 노드
|
|
3
|
+
|
|
4
|
+
커뮤니티에서 기여한 커스텀 노드 타입.
|
|
5
|
+
- messaging: TelegramNode 등 알림/메시징 노드
|
|
6
|
+
- analysis: PerformanceReportNode 등 성과 분석 노드
|
|
7
|
+
|
|
8
|
+
사용 방법:
|
|
9
|
+
from programgarden_community.nodes import TelegramNode, PerformanceReportNode
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from programgarden_community.nodes.messaging import TelegramNode
|
|
13
|
+
from programgarden_community.nodes.analysis import PerformanceReportNode
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"TelegramNode",
|
|
17
|
+
"PerformanceReportNode",
|
|
18
|
+
]
|
|
@@ -46,40 +46,6 @@ def register_all_nodes() -> None:
|
|
|
46
46
|
# 이미 등록된 경우 무시 (중복 호출 방지)
|
|
47
47
|
pass
|
|
48
48
|
|
|
49
|
-
# === Market Nodes ===
|
|
50
|
-
from programgarden_community.nodes.market import FearGreedIndexNode
|
|
51
|
-
|
|
52
|
-
market_nodes = [
|
|
53
|
-
FearGreedIndexNode,
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
for node_class in market_nodes:
|
|
57
|
-
try:
|
|
58
|
-
registry.register_community(
|
|
59
|
-
node_class,
|
|
60
|
-
source="community",
|
|
61
|
-
trust_level="community",
|
|
62
|
-
)
|
|
63
|
-
except ValueError as e:
|
|
64
|
-
pass
|
|
65
|
-
|
|
66
|
-
# === Data Nodes ===
|
|
67
|
-
from programgarden_community.nodes.data import FileReaderNode
|
|
68
|
-
|
|
69
|
-
data_nodes = [
|
|
70
|
-
FileReaderNode,
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
for node_class in data_nodes:
|
|
74
|
-
try:
|
|
75
|
-
registry.register_community(
|
|
76
|
-
node_class,
|
|
77
|
-
source="community",
|
|
78
|
-
trust_level="community",
|
|
79
|
-
)
|
|
80
|
-
except ValueError as e:
|
|
81
|
-
pass
|
|
82
|
-
|
|
83
49
|
# === Analysis Nodes ===
|
|
84
50
|
from programgarden_community.nodes.analysis import PerformanceReportNode
|
|
85
51
|
|
|
@@ -109,18 +75,6 @@ def get_community_node_list() -> list:
|
|
|
109
75
|
"description": "Send messages via Telegram Bot API",
|
|
110
76
|
"requires_credential": True,
|
|
111
77
|
},
|
|
112
|
-
{
|
|
113
|
-
"type": "FearGreedIndexNode",
|
|
114
|
-
"category": "market",
|
|
115
|
-
"description": "CNN Fear & Greed Index (0=Extreme Fear, 100=Extreme Greed)",
|
|
116
|
-
"requires_credential": False,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"type": "FileReaderNode",
|
|
120
|
-
"category": "data",
|
|
121
|
-
"description": "Read and parse files (PDF, TXT, CSV, JSON, MD)",
|
|
122
|
-
"requires_credential": False,
|
|
123
|
-
},
|
|
124
78
|
{
|
|
125
79
|
"type": "PerformanceReportNode",
|
|
126
80
|
"category": "analysis",
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden-community"
|
|
8
|
-
version = "2.0
|
|
8
|
+
version = "2.2.0"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden Community - 전략 플러그인 모음"
|
|
11
11
|
readme = "README.md"
|
|
@@ -16,7 +16,7 @@ packages = [
|
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[tool.poetry.dependencies]
|
|
19
|
-
programgarden-core = "^2.0
|
|
19
|
+
programgarden-core = "^2.3.0"
|
|
20
20
|
pypdf = ">=4.0.0"
|
|
21
21
|
# Optional heavy quant deps (extras). 3.14 wheels validated (plan §1.3).
|
|
22
22
|
# Only pyportfolioopt carries a python<3.15 marker to fence scikit-base (hard-capped
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ProgramGarden Community - 커뮤니티 노드
|
|
3
|
-
|
|
4
|
-
커뮤니티에서 기여한 커스텀 노드 타입.
|
|
5
|
-
- messaging: TelegramNode 등 알림/메시징 노드
|
|
6
|
-
- market: FearGreedIndexNode 등 외부 시장 데이터 노드
|
|
7
|
-
- data: FileReaderNode 등 데이터 처리 노드
|
|
8
|
-
|
|
9
|
-
사용 방법:
|
|
10
|
-
from programgarden_community.nodes import TelegramNode, FearGreedIndexNode, FileReaderNode
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from programgarden_community.nodes.messaging import TelegramNode
|
|
14
|
-
from programgarden_community.nodes.market import FearGreedIndexNode
|
|
15
|
-
from programgarden_community.nodes.data import FileReaderNode
|
|
16
|
-
from programgarden_community.nodes.analysis import PerformanceReportNode
|
|
17
|
-
|
|
18
|
-
__all__ = [
|
|
19
|
-
"TelegramNode",
|
|
20
|
-
"FearGreedIndexNode",
|
|
21
|
-
"FileReaderNode",
|
|
22
|
-
"PerformanceReportNode",
|
|
23
|
-
]
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ProgramGarden Community - 데이터 노드
|
|
3
|
-
|
|
4
|
-
파일 읽기/파싱 등 데이터 처리 노드.
|
|
5
|
-
|
|
6
|
-
사용 방법:
|
|
7
|
-
from programgarden_community.nodes.data import FileReaderNode
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from programgarden_community.nodes.data.file_reader import FileReaderNode
|
|
11
|
-
|
|
12
|
-
__all__ = [
|
|
13
|
-
"FileReaderNode",
|
|
14
|
-
]
|
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
FileReaderNode 내부 파서 모듈
|
|
3
|
-
|
|
4
|
-
포맷별 파일 파싱 함수. 외부에서 직접 사용하지 않음.
|
|
5
|
-
각 파서는 (text, data, extra_metadata) 튜플을 반환합니다.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import csv
|
|
9
|
-
import io
|
|
10
|
-
import json
|
|
11
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def parse_pdf(
|
|
15
|
-
file_bytes: bytes,
|
|
16
|
-
pages: Optional[str] = None,
|
|
17
|
-
extract_tables: bool = False,
|
|
18
|
-
) -> Tuple[str, Any, Dict[str, Any]]:
|
|
19
|
-
"""
|
|
20
|
-
PDF 파싱 (pypdf 사용)
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
file_bytes: PDF 파일 바이트
|
|
24
|
-
pages: 페이지 범위 ("1-5", "1,3,5", None=전체)
|
|
25
|
-
extract_tables: 테이블 추출 여부 (pdfplumber 필요)
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
(text, data, extra_metadata)
|
|
29
|
-
"""
|
|
30
|
-
try:
|
|
31
|
-
from pypdf import PdfReader
|
|
32
|
-
except ImportError:
|
|
33
|
-
raise ImportError(
|
|
34
|
-
"PDF 파싱에 pypdf가 필요합니다. "
|
|
35
|
-
"설치: pip install pypdf"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
reader = PdfReader(io.BytesIO(file_bytes))
|
|
39
|
-
total_pages = len(reader.pages)
|
|
40
|
-
|
|
41
|
-
# 페이지 범위 파싱
|
|
42
|
-
page_indices = _parse_page_range(pages, total_pages)
|
|
43
|
-
|
|
44
|
-
# 텍스트 추출
|
|
45
|
-
texts = []
|
|
46
|
-
for idx in page_indices:
|
|
47
|
-
page = reader.pages[idx]
|
|
48
|
-
text = page.extract_text() or ""
|
|
49
|
-
texts.append(text)
|
|
50
|
-
|
|
51
|
-
full_text = "\n".join(texts)
|
|
52
|
-
|
|
53
|
-
# 테이블 추출 (pdfplumber)
|
|
54
|
-
table_data = None
|
|
55
|
-
if extract_tables:
|
|
56
|
-
table_data = _extract_pdf_tables(file_bytes, page_indices)
|
|
57
|
-
|
|
58
|
-
extra = {
|
|
59
|
-
"page_count": total_pages,
|
|
60
|
-
"extracted_pages": len(page_indices),
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return full_text, table_data, extra
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _parse_page_range(pages: Optional[str], total: int) -> List[int]:
|
|
67
|
-
"""
|
|
68
|
-
페이지 범위 문자열 → 0-based 인덱스 리스트
|
|
69
|
-
|
|
70
|
-
Examples:
|
|
71
|
-
"1-5" → [0,1,2,3,4]
|
|
72
|
-
"1,3,5" → [0,2,4]
|
|
73
|
-
"2-4,7" → [1,2,3,6]
|
|
74
|
-
None → [0..total-1]
|
|
75
|
-
"""
|
|
76
|
-
if not pages:
|
|
77
|
-
return list(range(total))
|
|
78
|
-
|
|
79
|
-
indices = set()
|
|
80
|
-
for part in pages.split(","):
|
|
81
|
-
part = part.strip()
|
|
82
|
-
if "-" in part:
|
|
83
|
-
start_str, end_str = part.split("-", 1)
|
|
84
|
-
start = max(1, int(start_str.strip()))
|
|
85
|
-
end = min(total, int(end_str.strip()))
|
|
86
|
-
for i in range(start, end + 1):
|
|
87
|
-
indices.add(i - 1) # 0-based
|
|
88
|
-
else:
|
|
89
|
-
page_num = int(part)
|
|
90
|
-
if 1 <= page_num <= total:
|
|
91
|
-
indices.add(page_num - 1)
|
|
92
|
-
|
|
93
|
-
return sorted(indices)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _extract_pdf_tables(
|
|
97
|
-
file_bytes: bytes, page_indices: List[int]
|
|
98
|
-
) -> Optional[List[List[List[str]]]]:
|
|
99
|
-
"""pdfplumber로 PDF 테이블 추출"""
|
|
100
|
-
try:
|
|
101
|
-
import pdfplumber
|
|
102
|
-
except ImportError:
|
|
103
|
-
raise ImportError(
|
|
104
|
-
"PDF 테이블 추출에 pdfplumber가 필요합니다. "
|
|
105
|
-
"설치: pip install pdfplumber"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
tables = []
|
|
109
|
-
with pdfplumber.open(io.BytesIO(file_bytes)) as pdf:
|
|
110
|
-
for idx in page_indices:
|
|
111
|
-
if idx < len(pdf.pages):
|
|
112
|
-
page_tables = pdf.pages[idx].extract_tables()
|
|
113
|
-
if page_tables:
|
|
114
|
-
tables.extend(page_tables)
|
|
115
|
-
|
|
116
|
-
return tables if tables else None
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def parse_txt(
|
|
120
|
-
file_bytes: bytes,
|
|
121
|
-
encoding: str = "utf-8",
|
|
122
|
-
) -> Tuple[str, Any, Dict[str, Any]]:
|
|
123
|
-
"""TXT/MD 파싱"""
|
|
124
|
-
text = file_bytes.decode(encoding)
|
|
125
|
-
line_count = text.count("\n") + (1 if text and not text.endswith("\n") else 0)
|
|
126
|
-
return text, None, {"line_count": line_count}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def parse_csv(
|
|
130
|
-
file_bytes: bytes,
|
|
131
|
-
encoding: str = "utf-8",
|
|
132
|
-
delimiter: str = ",",
|
|
133
|
-
has_header: bool = True,
|
|
134
|
-
) -> Tuple[str, Any, Dict[str, Any]]:
|
|
135
|
-
"""CSV 파싱 → 구조화 데이터 (list[dict]) + 원본 텍스트"""
|
|
136
|
-
text = file_bytes.decode(encoding)
|
|
137
|
-
reader = csv.reader(io.StringIO(text), delimiter=delimiter)
|
|
138
|
-
rows = list(reader)
|
|
139
|
-
|
|
140
|
-
if not rows:
|
|
141
|
-
return text, [], {"row_count": 0, "column_count": 0}
|
|
142
|
-
|
|
143
|
-
if has_header and len(rows) > 1:
|
|
144
|
-
headers = rows[0]
|
|
145
|
-
data = [dict(zip(headers, row)) for row in rows[1:]]
|
|
146
|
-
row_count = len(data)
|
|
147
|
-
elif has_header and len(rows) == 1:
|
|
148
|
-
# 헤더만 있는 경우
|
|
149
|
-
data = []
|
|
150
|
-
row_count = 0
|
|
151
|
-
else:
|
|
152
|
-
data = rows
|
|
153
|
-
row_count = len(rows)
|
|
154
|
-
|
|
155
|
-
column_count = len(rows[0]) if rows else 0
|
|
156
|
-
|
|
157
|
-
return text, data, {"row_count": row_count, "column_count": column_count}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def parse_json(
|
|
161
|
-
file_bytes: bytes,
|
|
162
|
-
encoding: str = "utf-8",
|
|
163
|
-
) -> Tuple[str, Any, Dict[str, Any]]:
|
|
164
|
-
"""JSON 파싱 → 파싱된 객체 + 원본 텍스트"""
|
|
165
|
-
text = file_bytes.decode(encoding)
|
|
166
|
-
data = json.loads(text)
|
|
167
|
-
|
|
168
|
-
extra: Dict[str, Any] = {}
|
|
169
|
-
if isinstance(data, list):
|
|
170
|
-
extra["item_count"] = len(data)
|
|
171
|
-
elif isinstance(data, dict):
|
|
172
|
-
extra["key_count"] = len(data)
|
|
173
|
-
|
|
174
|
-
return text, data, extra
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def parse_docx(
|
|
178
|
-
file_bytes: bytes,
|
|
179
|
-
) -> Tuple[str, Any, Dict[str, Any]]:
|
|
180
|
-
"""
|
|
181
|
-
DOCX 파싱 (python-docx 사용)
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
(text, data, extra_metadata)
|
|
185
|
-
- text: 전체 문단 텍스트 (줄바꿈 연결)
|
|
186
|
-
- data: 테이블 목록 (list[list[list[str]]]) 또는 None
|
|
187
|
-
- extra_metadata: paragraph_count, table_count
|
|
188
|
-
"""
|
|
189
|
-
try:
|
|
190
|
-
from docx import Document
|
|
191
|
-
except ImportError:
|
|
192
|
-
raise ImportError(
|
|
193
|
-
"DOCX 파싱에 python-docx가 필요합니다. "
|
|
194
|
-
"설치: pip install python-docx"
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
doc = Document(io.BytesIO(file_bytes))
|
|
198
|
-
|
|
199
|
-
# 문단 텍스트 추출
|
|
200
|
-
paragraphs = [p.text for p in doc.paragraphs]
|
|
201
|
-
full_text = "\n".join(paragraphs)
|
|
202
|
-
|
|
203
|
-
# 테이블 추출
|
|
204
|
-
tables = []
|
|
205
|
-
for table in doc.tables:
|
|
206
|
-
rows = []
|
|
207
|
-
for row in table.rows:
|
|
208
|
-
rows.append([cell.text for cell in row.cells])
|
|
209
|
-
tables.append(rows)
|
|
210
|
-
|
|
211
|
-
table_data = tables if tables else None
|
|
212
|
-
|
|
213
|
-
extra = {
|
|
214
|
-
"paragraph_count": len(paragraphs),
|
|
215
|
-
"table_count": len(doc.tables),
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return full_text, table_data, extra
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def parse_xlsx(
|
|
222
|
-
file_bytes: bytes,
|
|
223
|
-
sheet_name: Optional[str] = None,
|
|
224
|
-
) -> Tuple[str, Any, Dict[str, Any]]:
|
|
225
|
-
"""
|
|
226
|
-
XLSX 파싱 (openpyxl 사용)
|
|
227
|
-
|
|
228
|
-
Args:
|
|
229
|
-
file_bytes: XLSX 파일 바이트
|
|
230
|
-
sheet_name: 시트명 (None=활성 시트)
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
(text, data, extra_metadata)
|
|
234
|
-
- text: CSV 형식 텍스트
|
|
235
|
-
- data: list[dict] (첫 행을 헤더로 사용) 또는 list[list]
|
|
236
|
-
- extra_metadata: sheet_name, row_count, column_count, sheet_names
|
|
237
|
-
"""
|
|
238
|
-
try:
|
|
239
|
-
from openpyxl import load_workbook
|
|
240
|
-
except ImportError:
|
|
241
|
-
raise ImportError(
|
|
242
|
-
"XLSX 파싱에 openpyxl이 필요합니다. "
|
|
243
|
-
"설치: pip install openpyxl"
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
wb = load_workbook(io.BytesIO(file_bytes), read_only=True, data_only=True)
|
|
247
|
-
sheet_names = wb.sheetnames
|
|
248
|
-
|
|
249
|
-
if sheet_name and sheet_name in sheet_names:
|
|
250
|
-
ws = wb[sheet_name]
|
|
251
|
-
else:
|
|
252
|
-
ws = wb.active
|
|
253
|
-
sheet_name = ws.title
|
|
254
|
-
|
|
255
|
-
# 모든 행 읽기
|
|
256
|
-
rows = []
|
|
257
|
-
for row in ws.iter_rows(values_only=True):
|
|
258
|
-
rows.append([str(cell) if cell is not None else "" for cell in row])
|
|
259
|
-
|
|
260
|
-
wb.close()
|
|
261
|
-
|
|
262
|
-
if not rows:
|
|
263
|
-
return "", [], {
|
|
264
|
-
"sheet_name": sheet_name,
|
|
265
|
-
"row_count": 0,
|
|
266
|
-
"column_count": 0,
|
|
267
|
-
"sheet_names": sheet_names,
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
# 첫 행을 헤더로 사용
|
|
271
|
-
headers = rows[0]
|
|
272
|
-
column_count = len(headers)
|
|
273
|
-
|
|
274
|
-
if len(rows) > 1:
|
|
275
|
-
data = [dict(zip(headers, row)) for row in rows[1:]]
|
|
276
|
-
row_count = len(data)
|
|
277
|
-
else:
|
|
278
|
-
data = []
|
|
279
|
-
row_count = 0
|
|
280
|
-
|
|
281
|
-
# CSV 형식 텍스트 생성
|
|
282
|
-
text_lines = [",".join(row) for row in rows]
|
|
283
|
-
text = "\n".join(text_lines)
|
|
284
|
-
|
|
285
|
-
extra = {
|
|
286
|
-
"sheet_name": sheet_name,
|
|
287
|
-
"row_count": row_count,
|
|
288
|
-
"column_count": column_count,
|
|
289
|
-
"sheet_names": sheet_names,
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return text, data, extra
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# 포맷 → 확장자 매핑
|
|
296
|
-
EXTENSION_MAP = {
|
|
297
|
-
".pdf": "pdf",
|
|
298
|
-
".txt": "txt",
|
|
299
|
-
".csv": "csv",
|
|
300
|
-
".json": "json",
|
|
301
|
-
".md": "md",
|
|
302
|
-
".docx": "docx",
|
|
303
|
-
".xlsx": "xlsx",
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
# PDF 매직 바이트
|
|
307
|
-
_PDF_MAGIC = b"%PDF-"
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def detect_format(
|
|
311
|
-
file_name: Optional[str] = None,
|
|
312
|
-
file_path: Optional[str] = None,
|
|
313
|
-
file_bytes: Optional[bytes] = None,
|
|
314
|
-
) -> str:
|
|
315
|
-
"""
|
|
316
|
-
파일 포맷 자동 감지
|
|
317
|
-
|
|
318
|
-
우선순위:
|
|
319
|
-
1. file_name 확장자
|
|
320
|
-
2. file_path 확장자
|
|
321
|
-
3. 매직 바이트 (PDF)
|
|
322
|
-
4. txt fallback
|
|
323
|
-
"""
|
|
324
|
-
import os
|
|
325
|
-
|
|
326
|
-
# 1. file_name 확장자
|
|
327
|
-
if file_name:
|
|
328
|
-
ext = os.path.splitext(file_name)[1].lower()
|
|
329
|
-
if ext in EXTENSION_MAP:
|
|
330
|
-
return EXTENSION_MAP[ext]
|
|
331
|
-
|
|
332
|
-
# 2. file_path 확장자
|
|
333
|
-
if file_path:
|
|
334
|
-
ext = os.path.splitext(file_path)[1].lower()
|
|
335
|
-
if ext in EXTENSION_MAP:
|
|
336
|
-
return EXTENSION_MAP[ext]
|
|
337
|
-
|
|
338
|
-
# 3. 매직 바이트
|
|
339
|
-
if file_bytes and file_bytes[:5] == _PDF_MAGIC:
|
|
340
|
-
return "pdf"
|
|
341
|
-
|
|
342
|
-
# 4. fallback
|
|
343
|
-
return "txt"
|