neurostats-API 0.0.21b0__tar.gz → 0.0.23__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.
- neurostats_api-0.0.23/MANIFEST.in +9 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/PKG-INFO +2 -2
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/README.md +1 -1
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/__init__.py +1 -1
- neurostats_api-0.0.23/neurostats_API/fetchers/balance_sheet.py +201 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/fetchers/base.py +93 -74
- neurostats_api-0.0.23/neurostats_API/fetchers/cash_flow.py +221 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/fetchers/finance_overview.py +28 -28
- neurostats_api-0.0.23/neurostats_API/fetchers/institution.py +413 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/fetchers/margin_trading.py +121 -94
- neurostats_api-0.0.23/neurostats_API/fetchers/month_revenue.py +195 -0
- neurostats_api-0.0.23/neurostats_API/fetchers/profit_lose.py +253 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/fetchers/tech.py +117 -42
- neurostats_api-0.0.23/neurostats_API/fetchers/tej_finance_report.py +376 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/fetchers/value_invest.py +32 -12
- neurostats_api-0.0.23/neurostats_API/tools/company_list/tw.json +2175 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/tools/tej_db/tej_db_percent_index.yaml +0 -3
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/tools/tej_db/tej_db_skip_index.yaml +14 -1
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/tools/tej_db/tej_db_thousand_index.yaml +0 -5
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/utils/__init__.py +0 -1
- neurostats_api-0.0.23/neurostats_API/utils/calculate_value.py +127 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/utils/data_process.py +53 -19
- neurostats_api-0.0.23/neurostats_API/utils/logger.py +21 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API.egg-info/PKG-INFO +2 -2
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API.egg-info/SOURCES.txt +7 -6
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/setup.py +2 -3
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/test/test_fetchers.py +46 -23
- neurostats_api-0.0.21b0/MANIFEST.in +0 -2
- neurostats_api-0.0.21b0/neurostats_API/fetchers/balance_sheet.py +0 -151
- neurostats_api-0.0.21b0/neurostats_API/fetchers/cash_flow.py +0 -191
- neurostats_api-0.0.21b0/neurostats_API/fetchers/institution.py +0 -299
- neurostats_api-0.0.21b0/neurostats_API/fetchers/month_revenue.py +0 -161
- neurostats_api-0.0.21b0/neurostats_API/fetchers/profit_lose.py +0 -158
- neurostats_api-0.0.21b0/neurostats_API/fetchers/tej_finance_report.py +0 -466
- neurostats_api-0.0.21b0/neurostats_API/utils/calculate_value.py +0 -26
- neurostats_api-0.0.21b0/neurostats_API/utils/fetcher.py +0 -1056
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/cli.py +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/fetchers/__init__.py +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/main.py +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/tools/tej_db/tej_db_index.yaml +0 -0
- {neurostats_api-0.0.21b0/neurostats_API/tools → neurostats_api-0.0.23/neurostats_API/tools/twse}/balance_sheet.yaml +0 -0
- {neurostats_api-0.0.21b0/neurostats_API/tools → neurostats_api-0.0.23/neurostats_API/tools/twse}/cash_flow_percentage.yaml +0 -0
- {neurostats_api-0.0.21b0/neurostats_API/tools → neurostats_api-0.0.23/neurostats_API/tools/twse}/finance_overview_dict.yaml +0 -0
- {neurostats_api-0.0.21b0/neurostats_API/tools → neurostats_api-0.0.23/neurostats_API/tools/twse}/profit_lose.yaml +0 -0
- {neurostats_api-0.0.21b0/neurostats_API/tools → neurostats_api-0.0.23/neurostats_API/tools/twse}/seasonal_data_field_dict.txt +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/utils/datetime.py +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API/utils/db_client.py +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API.egg-info/dependency_links.txt +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API.egg-info/requires.txt +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/neurostats_API.egg-info/top_level.txt +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/setup.cfg +0 -0
- {neurostats_api-0.0.21b0 → neurostats_api-0.0.23}/test/test_tej.py +0 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
recursive-include neurostats_api/tools/tej_db *.yaml
|
2
|
+
recursive-include neurostats_api/tools/tej_db *.txt
|
3
|
+
recursive-include neurostats_api/tools/tej_db *.json
|
4
|
+
recursive-include neurostats_api/tools/twse *.yaml
|
5
|
+
recursive-include neurostats_api/tools/twse *.txt
|
6
|
+
recursive-include neurostats_api/tools/twse *.json
|
7
|
+
recursive-include neurostats_api/tools/company_list *.yaml
|
8
|
+
recursive-include neurostats_api/tools/company_list *.txt
|
9
|
+
recursive-include neurostats_api/tools/company_list *.json
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: neurostats_API
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.23
|
4
4
|
Summary: The service of NeuroStats website
|
5
5
|
Home-page: https://github.com/NeurowattStats/NeuroStats_API.git
|
6
6
|
Author: JasonWang@Neurowatt
|
@@ -89,7 +89,7 @@ pip install neurostats-API
|
|
89
89
|
```Python
|
90
90
|
>>> import neurostats_API
|
91
91
|
>>> print(neurostats_API.__version__)
|
92
|
-
0.0.
|
92
|
+
0.0.23
|
93
93
|
```
|
94
94
|
|
95
95
|
### 得到最新一期的評價資料與歷年評價
|
@@ -0,0 +1,201 @@
|
|
1
|
+
from .base import StatsFetcher, StatsDateTime
|
2
|
+
import json
|
3
|
+
import pandas as pd
|
4
|
+
from ..utils import StatsDateTime, StatsProcessor
|
5
|
+
import yaml
|
6
|
+
|
7
|
+
|
8
|
+
class BalanceSheetFetcher(StatsFetcher):
|
9
|
+
"""
|
10
|
+
對應iFa.ai -> 財務分析 -> 資產負債表
|
11
|
+
"""
|
12
|
+
|
13
|
+
def __init__(self, ticker, db_client):
|
14
|
+
super().__init__(ticker, db_client)
|
15
|
+
self.table_settings = StatsProcessor.load_yaml("twse/balance_sheet.yaml")
|
16
|
+
|
17
|
+
self.process_function_map = {
|
18
|
+
"twse_stats": self.process_data_twse,
|
19
|
+
"us_stats": self.process_data_us
|
20
|
+
}
|
21
|
+
|
22
|
+
self.return_keys = [
|
23
|
+
'balance_sheet', 'total_asset', 'current_asset', 'non_current_asset',
|
24
|
+
'current_debt', 'non_current_debt', 'equity', 'balance_sheet_all', 'balance_sheet_YoY'
|
25
|
+
]
|
26
|
+
|
27
|
+
def prepare_query(self):
|
28
|
+
pipeline = super().prepare_query()
|
29
|
+
|
30
|
+
name_map = {
|
31
|
+
"twse_stats": "balance_sheet",
|
32
|
+
"us_stats": "balance_sheet"
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
chart_name = name_map.get(self.collection_name, "balance_sheet")
|
37
|
+
|
38
|
+
append_pipeline = [
|
39
|
+
{
|
40
|
+
"$project": {
|
41
|
+
"_id": 0,
|
42
|
+
"ticker": 1,
|
43
|
+
"company_name": 1,
|
44
|
+
"seasonal_data": {
|
45
|
+
"$map": {
|
46
|
+
"input": {"$ifNull": ["$seasonal_data", []]},
|
47
|
+
"as": "season",
|
48
|
+
"in": {
|
49
|
+
"year": "$$season.year",
|
50
|
+
"season": "$$season.season",
|
51
|
+
"data": {"$ifNull": [f"$$season.{chart_name}", []]}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
]
|
58
|
+
pipeline = pipeline + append_pipeline
|
59
|
+
|
60
|
+
return pipeline
|
61
|
+
|
62
|
+
def collect_data(self):
|
63
|
+
return super().collect_data()
|
64
|
+
|
65
|
+
def query_data(self):
|
66
|
+
fetched_data = self.collect_data()
|
67
|
+
fetched_data = fetched_data[0]
|
68
|
+
|
69
|
+
process_fn = self.process_function_map[self.collection_name]
|
70
|
+
processed_data = process_fn(fetched_data)
|
71
|
+
return processed_data
|
72
|
+
|
73
|
+
def process_data_twse(self, fetched_data):
|
74
|
+
latest_time = StatsDateTime.get_latest_time(
|
75
|
+
self.ticker, self.collection
|
76
|
+
).get('last_update_time', {})
|
77
|
+
# 取最新時間資料時間,沒取到就預設去年年底
|
78
|
+
target_year = latest_time.get('seasonal_data', {}).get(
|
79
|
+
'latest_target_year',
|
80
|
+
StatsDateTime.get_today().year - 1
|
81
|
+
)
|
82
|
+
target_season = latest_time.get('seasonal_data',
|
83
|
+
{}).get('latest_season', 4)
|
84
|
+
|
85
|
+
return_dict = {
|
86
|
+
"ticker": self.ticker,
|
87
|
+
"company_name": fetched_data['company_name']
|
88
|
+
}
|
89
|
+
table_dict = {}
|
90
|
+
|
91
|
+
seasonal_data = fetched_data.get('seasonal_data')
|
92
|
+
if not seasonal_data:
|
93
|
+
return_dict.update(self._get_empty_structure())
|
94
|
+
return return_dict
|
95
|
+
|
96
|
+
# 將value與percentage跟著年分季度一筆筆取出
|
97
|
+
for data in seasonal_data:
|
98
|
+
year, season, balance_sheet = data['year'], data['season'], data[
|
99
|
+
'data']
|
100
|
+
time_index = f"{year}Q{season}"
|
101
|
+
|
102
|
+
new_balance_sheet = dict()
|
103
|
+
# 蒐集整體的keys
|
104
|
+
index_names = list(balance_sheet.keys())
|
105
|
+
|
106
|
+
table_dict[time_index] = balance_sheet
|
107
|
+
# flatten dict:
|
108
|
+
# {<key>: {"value": <value>, "percentage": <value>}}
|
109
|
+
# -> {<key>_value: <value>, <key>_percentage:<value>}
|
110
|
+
|
111
|
+
old_balance_sheet = pd.DataFrame(table_dict)
|
112
|
+
target_season_col = old_balance_sheet.columns.str.endswith(f"Q{target_season}")
|
113
|
+
old_balance_sheet = old_balance_sheet.loc[:, target_season_col]
|
114
|
+
old_balance_sheet = StatsProcessor.expand_value_percentage(old_balance_sheet)
|
115
|
+
|
116
|
+
# 處理QoQ版BalanceSheet
|
117
|
+
for time_index, data_dict in table_dict.items():
|
118
|
+
new_balance_sheet = self.flatten_dict(
|
119
|
+
data_dict,
|
120
|
+
indexes = index_names,
|
121
|
+
target_keys=["value", "percentage"]
|
122
|
+
)
|
123
|
+
|
124
|
+
table_dict[time_index] = new_balance_sheet
|
125
|
+
|
126
|
+
total_table = pd.DataFrame.from_dict(table_dict).T
|
127
|
+
value_index = total_table.columns.str.endswith("_value")
|
128
|
+
value_cols = total_table.loc[:, value_index].columns
|
129
|
+
total_table[value_cols] = (
|
130
|
+
total_table[value_cols].map(
|
131
|
+
lambda x: StatsProcessor.cal_non_percentage(x, postfix="千元"),
|
132
|
+
)
|
133
|
+
)
|
134
|
+
|
135
|
+
percentage_index = total_table.columns.str.endswith(
|
136
|
+
"_percentage"
|
137
|
+
)
|
138
|
+
percentage_cols = total_table.loc[:, percentage_index].columns
|
139
|
+
total_table[percentage_cols] = (
|
140
|
+
total_table[percentage_cols].map(
|
141
|
+
lambda x: StatsProcessor.
|
142
|
+
cal_non_percentage(x, to_str=True, postfix="%"),
|
143
|
+
)
|
144
|
+
)
|
145
|
+
|
146
|
+
total_table = total_table.T
|
147
|
+
target_season_columns = total_table.columns.str.endswith(
|
148
|
+
f"Q{target_season}"
|
149
|
+
)
|
150
|
+
total_table_YoY = total_table.loc[:, target_season_columns]
|
151
|
+
|
152
|
+
for name, setting in self.table_settings.items():
|
153
|
+
target_indexes = setting.get('target_index', [None])
|
154
|
+
for target_index in target_indexes:
|
155
|
+
try:
|
156
|
+
return_dict[name] = StatsProcessor.slice_old_table(
|
157
|
+
total_table=old_balance_sheet,
|
158
|
+
target_index=target_index
|
159
|
+
)
|
160
|
+
break
|
161
|
+
except Exception as e:
|
162
|
+
continue
|
163
|
+
|
164
|
+
return_dict.update(
|
165
|
+
{
|
166
|
+
"balance_sheet": old_balance_sheet,
|
167
|
+
"balance_sheet_all": total_table.copy(),
|
168
|
+
"balance_sheet_YoY": total_table_YoY
|
169
|
+
}
|
170
|
+
)
|
171
|
+
return return_dict
|
172
|
+
|
173
|
+
def process_data_us(self, fetched_data):
|
174
|
+
return_dict = {
|
175
|
+
"ticker": self.ticker,
|
176
|
+
"company_name": fetched_data[-1]['company_name']
|
177
|
+
}
|
178
|
+
|
179
|
+
table_dict = dict()
|
180
|
+
|
181
|
+
for data in fetched_data:
|
182
|
+
year, season, balance_sheet = data['year'], data['season'], data[
|
183
|
+
'balance_sheet']
|
184
|
+
table_dict[f"{year}Q{season}"] = balance_sheet
|
185
|
+
|
186
|
+
table_dict = pd.DataFrame.from_dict(table_dict)
|
187
|
+
|
188
|
+
return_dict["balance_sheet"] = table_dict
|
189
|
+
|
190
|
+
latest_season = fetched_data[0]['season']
|
191
|
+
target_season_columns = table_dict.columns.str.endswith(
|
192
|
+
f"Q{latest_season}"
|
193
|
+
)
|
194
|
+
table_dict_YoY = table_dict.loc[:, target_season_columns]
|
195
|
+
return_dict["balance_sheet_YoY"] = table_dict_YoY
|
196
|
+
return return_dict
|
197
|
+
|
198
|
+
def _get_empty_structure(self):
|
199
|
+
return {
|
200
|
+
key: pd.DataFrame(columns= pd.Index([], name = 'date')) for key in self.return_keys
|
201
|
+
}
|
@@ -1,22 +1,31 @@
|
|
1
1
|
import abc
|
2
|
-
from
|
3
|
-
from pymongo import MongoClient
|
4
|
-
import pandas as pd
|
2
|
+
from datetime import datetime, timedelta, date
|
5
3
|
import json
|
4
|
+
import pandas as pd
|
5
|
+
from pymongo import MongoClient
|
6
6
|
import pytz
|
7
|
-
from
|
7
|
+
from typing import Union
|
8
8
|
from ..utils import StatsDateTime, StatsProcessor, YoY_Calculator
|
9
|
-
import yaml
|
10
9
|
|
11
10
|
|
12
|
-
class StatsFetcher:
|
11
|
+
class StatsFetcher(abc.ABC):
|
13
12
|
|
14
|
-
def __init__(self, ticker, db_client):
|
13
|
+
def __init__(self, ticker: str, db_client: MongoClient):
|
15
14
|
self.ticker = ticker
|
16
|
-
self.db = db_client["company"] # Replace with your database name
|
17
|
-
self.collection = self.db["twse_stats"]
|
18
|
-
|
19
15
|
self.timezone = pytz.timezone("Asia/Taipei")
|
16
|
+
self.tw_company_list = StatsProcessor.load_json("company_list/tw.json")
|
17
|
+
db_mapping = {
|
18
|
+
"company": "twse_stats",
|
19
|
+
"company_us": "us_stats",
|
20
|
+
}
|
21
|
+
|
22
|
+
name_mapping = {"company": "台股", "company_us": "美股"}
|
23
|
+
|
24
|
+
db_name = "company" if self.ticker in self.tw_company_list else "company_us"
|
25
|
+
self.db = db_client[db_name]
|
26
|
+
self.collection_name = db_mapping.get(db_name, "unknown")
|
27
|
+
assert self.collection_name != "unknown", f"請確認 {ticker} 是否是 {','.join(list(name_mapping.values()))}"
|
28
|
+
self.collection = db_client[db_name][self.collection_name]
|
20
29
|
|
21
30
|
self.target_metric_dict = {
|
22
31
|
'value': ['value'],
|
@@ -37,40 +46,41 @@ class StatsFetcher:
|
|
37
46
|
}
|
38
47
|
},
|
39
48
|
]
|
49
|
+
|
50
|
+
def query_data(self):
|
51
|
+
return NotImplementedError()
|
40
52
|
|
41
|
-
def collect_data(self
|
53
|
+
def collect_data(self):
|
42
54
|
pipeline = self.prepare_query()
|
43
|
-
|
44
55
|
fetched_data = list(self.collection.aggregate(pipeline))
|
56
|
+
return fetched_data if fetched_data else None
|
45
57
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
year, month, day = [int(num) for num in date_str.split("-")]
|
50
|
-
|
51
|
-
date = datetime.strptime(date_str, "%Y-%m-%d")
|
52
|
-
date = self.timezone.localize(date)
|
53
|
-
|
58
|
+
def str_to_datetime(self, date_str: str) -> StatsDateTime:
|
59
|
+
date = self.timezone.localize(datetime.strptime(date_str, "%Y-%m-%d"))
|
60
|
+
year, month, day = date.year, date.month, date.day
|
54
61
|
season = (month - 1) // 3 + 1
|
55
|
-
|
56
62
|
return StatsDateTime(date, year, month, day, season)
|
57
63
|
|
58
|
-
def has_required_columns(
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
Args:
|
63
|
-
df (pd.DataFrame): The DataFrame to check.
|
64
|
-
required_cols (list, optional): List of required column names.
|
65
|
-
Defaults to ['date', 'open', 'high', 'low', 'close', 'volume'].
|
66
|
-
|
67
|
-
Returns:
|
68
|
-
bool: True if all required columns are present, False otherwise.
|
69
|
-
"""
|
64
|
+
def has_required_columns(
|
65
|
+
self, df: pd.DataFrame, required_cols=None
|
66
|
+
) -> bool:
|
70
67
|
if required_cols is None:
|
71
68
|
required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
|
72
|
-
|
73
69
|
return all(col in df.columns for col in required_cols)
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def flatten_dict(value_dict, indexes, target_keys):
|
73
|
+
indexes = value_dict.keys()
|
74
|
+
new_dict = {}
|
75
|
+
|
76
|
+
for key in indexes:
|
77
|
+
new_dict.update(
|
78
|
+
{
|
79
|
+
f"{key}_{sub_key}": value_dict[key].get(sub_key, None)
|
80
|
+
for sub_key in target_keys
|
81
|
+
}
|
82
|
+
)
|
83
|
+
return new_dict
|
74
84
|
|
75
85
|
|
76
86
|
class BaseTEJFetcher(abc.ABC):
|
@@ -81,21 +91,18 @@ class BaseTEJFetcher(abc.ABC):
|
|
81
91
|
|
82
92
|
def get_latest_data_time(self, ticker):
|
83
93
|
latest_data = self.collection.find_one(
|
84
|
-
{
|
85
|
-
"ticker": ticker
|
86
|
-
},
|
87
|
-
{
|
94
|
+
{"ticker": ticker}, {
|
88
95
|
"last_update": 1,
|
89
96
|
"_id": 0
|
90
97
|
}
|
91
98
|
)
|
92
99
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
100
|
+
if (latest_data):
|
101
|
+
# return 得到最新日期或None
|
102
|
+
return latest_data.get('last_update', {}).get("latest_data_date", None)
|
103
|
+
|
104
|
+
else:
|
105
|
+
return None
|
99
106
|
|
100
107
|
def process_value(self, value):
|
101
108
|
if isinstance(value, str) and "%" in value:
|
@@ -107,77 +114,89 @@ class BaseTEJFetcher(abc.ABC):
|
|
107
114
|
|
108
115
|
def calculate_growth(self, this_value, last_value, delta):
|
109
116
|
try:
|
110
|
-
return YoY_Calculator.cal_growth(
|
117
|
+
return YoY_Calculator.cal_growth(
|
118
|
+
this_value, last_value, delta
|
119
|
+
) * 100
|
111
120
|
except Exception:
|
112
121
|
return None
|
113
122
|
|
114
|
-
def cal_YoY(
|
123
|
+
def cal_YoY(
|
124
|
+
self, data_dict: dict, start_year: int, end_year: int, season: int
|
125
|
+
):
|
115
126
|
year_shifts = [1, 3, 5, 10]
|
116
127
|
return_dict = {}
|
117
|
-
|
128
|
+
|
118
129
|
for year in range(start_year, end_year + 1):
|
119
130
|
year_data = data_dict.get(f"{year}Q{season}", {}).copy()
|
120
131
|
if not year_data:
|
121
132
|
continue
|
122
|
-
|
123
|
-
for key in list(year_data.
|
133
|
+
|
134
|
+
for key, value in list(year_data.items()):
|
124
135
|
if key == "season":
|
125
136
|
continue
|
126
|
-
|
127
|
-
this_value = self.process_value(
|
137
|
+
|
138
|
+
this_value = self.process_value(value)
|
128
139
|
if this_value is None:
|
129
140
|
year_data.pop(key)
|
130
141
|
continue
|
131
|
-
|
132
|
-
temp_dict = {"value":
|
142
|
+
|
143
|
+
temp_dict = {"value": value}
|
133
144
|
for shift in year_shifts:
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
growth = self.calculate_growth(this_value,
|
145
|
+
past_value = self.process_value(
|
146
|
+
data_dict.get(f"{year - shift}Q{season}", {}).get(key)
|
147
|
+
)
|
148
|
+
growth = self.calculate_growth(this_value, past_value, shift) if past_value else None
|
149
|
+
|
150
|
+
temp_dict[
|
151
|
+
f"YoY_{shift}"
|
152
|
+
] = f"{growth:.2f}%" if growth else None
|
138
153
|
|
139
|
-
temp_dict[f"YoY_{shift}"] = (f"{growth:.2f}%" if growth else None)
|
140
154
|
year_data[key] = temp_dict
|
141
|
-
|
142
155
|
return_dict[f"{year}Q{season}"] = year_data
|
143
|
-
|
144
156
|
return return_dict
|
145
157
|
|
146
158
|
def cal_QoQ(self, data_dict):
|
147
159
|
return_dict = {}
|
148
|
-
|
160
|
+
|
149
161
|
for time_index, this_data in data_dict.items():
|
150
162
|
year, season = map(int, time_index.split("Q"))
|
151
|
-
last_year, last_season = (
|
152
|
-
|
163
|
+
last_year, last_season = (
|
164
|
+
year - 1, 4
|
165
|
+
) if season == 1 else (year, season - 1)
|
166
|
+
|
153
167
|
for key in list(this_data.keys()):
|
154
168
|
if key == "season":
|
155
169
|
continue
|
156
|
-
|
170
|
+
|
157
171
|
this_value = self.process_value(this_data[key])
|
158
172
|
if this_value is None:
|
159
173
|
this_data.pop(key)
|
160
174
|
continue
|
161
|
-
|
175
|
+
|
162
176
|
temp_dict = {"value": this_data[key]}
|
163
|
-
last_value = data_dict.get(
|
177
|
+
last_value = data_dict.get(
|
178
|
+
f"{last_year}Q{last_season}",{}
|
179
|
+
).get(key, {}).get('value')
|
180
|
+
|
164
181
|
last_value = self.process_value(last_value)
|
165
|
-
growth = self.calculate_growth(
|
182
|
+
growth = self.calculate_growth(
|
183
|
+
this_value, last_value, 1
|
184
|
+
) if last_value is not None else None
|
166
185
|
temp_dict['growth'] = (f"{growth:.2f}%" if growth else None)
|
167
|
-
|
186
|
+
|
168
187
|
this_data[key] = temp_dict
|
169
|
-
|
188
|
+
|
170
189
|
return_dict[time_index] = this_data
|
171
|
-
|
190
|
+
|
172
191
|
return return_dict
|
173
192
|
|
174
193
|
def get_dict_of_df(self, data_dict):
|
175
194
|
"""
|
176
195
|
dict[dict] -> dict[df]
|
177
196
|
"""
|
178
|
-
|
179
|
-
|
180
|
-
|
197
|
+
return {
|
198
|
+
key: pd.DataFrame.from_dict(data) for key, data in data_dict.items()
|
199
|
+
}
|
181
200
|
|
182
201
|
def set_time_shift(self, date: Union[str, datetime], period: str):
|
183
202
|
if isinstance(date, str):
|
@@ -197,4 +216,4 @@ class BaseTEJFetcher(abc.ABC):
|
|
197
216
|
if period == "all":
|
198
217
|
return datetime.strptime("1991-01-01", "%Y-%m-%d")
|
199
218
|
|
200
|
-
return date - period_mapping.get(period, timedelta(days=0))
|
219
|
+
return date - period_mapping.get(period, timedelta(days=0)) # 預設為不變"
|