neurostats-API 0.0.21b0__py3-none-any.whl → 0.0.23b0__py3-none-any.whl
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/__init__.py +1 -1
- neurostats_API/fetchers/balance_sheet.py +138 -111
- neurostats_API/fetchers/base.py +89 -74
- neurostats_API/fetchers/cash_flow.py +120 -111
- neurostats_API/fetchers/finance_overview.py +2 -2
- neurostats_API/fetchers/month_revenue.py +1 -1
- neurostats_API/fetchers/profit_lose.py +188 -113
- neurostats_API/fetchers/tech.py +175 -42
- neurostats_API/fetchers/tej_finance_report.py +230 -335
- neurostats_API/tools/company_list/tw.json +2175 -0
- neurostats_API/tools/tej_db/tej_db_skip_index.yaml +3 -1
- neurostats_API/tools/tej_db/tej_db_thousand_index.yaml +0 -1
- neurostats_API/utils/__init__.py +0 -1
- neurostats_API/utils/calculate_value.py +99 -1
- neurostats_API/utils/data_process.py +43 -15
- {neurostats_API-0.0.21b0.dist-info → neurostats_API-0.0.23b0.dist-info}/METADATA +2 -2
- neurostats_API-0.0.23b0.dist-info/RECORD +34 -0
- neurostats_API/utils/fetcher.py +0 -1056
- neurostats_API-0.0.21b0.dist-info/RECORD +0 -34
- /neurostats_API/tools/{balance_sheet.yaml → twse/balance_sheet.yaml} +0 -0
- /neurostats_API/tools/{cash_flow_percentage.yaml → twse/cash_flow_percentage.yaml} +0 -0
- /neurostats_API/tools/{finance_overview_dict.yaml → twse/finance_overview_dict.yaml} +0 -0
- /neurostats_API/tools/{profit_lose.yaml → twse/profit_lose.yaml} +0 -0
- /neurostats_API/tools/{seasonal_data_field_dict.txt → twse/seasonal_data_field_dict.txt} +0 -0
- {neurostats_API-0.0.21b0.dist-info → neurostats_API-0.0.23b0.dist-info}/WHEEL +0 -0
- {neurostats_API-0.0.21b0.dist-info → neurostats_API-0.0.23b0.dist-info}/top_level.txt +0 -0
@@ -11,81 +11,69 @@ class CashFlowFetcher(StatsFetcher):
|
|
11
11
|
super().__init__(ticker, db_client)
|
12
12
|
|
13
13
|
self.cash_flow_dict = StatsProcessor.load_yaml(
|
14
|
-
"cash_flow_percentage.yaml"
|
14
|
+
"twse/cash_flow_percentage.yaml"
|
15
15
|
) # 計算子表格用
|
16
|
+
|
17
|
+
self.process_function_map = {
|
18
|
+
"twse_stats": self.process_data_twse,
|
19
|
+
"us_stats": self.process_data_us
|
20
|
+
}
|
16
21
|
|
17
|
-
def prepare_query(self
|
22
|
+
def prepare_query(self):
|
18
23
|
pipeline = super().prepare_query()
|
19
24
|
|
20
|
-
|
21
|
-
"
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
}
|
49
|
-
},
|
50
|
-
"sortBy": {
|
51
|
-
"year": -1
|
52
|
-
} # 按 year 降序排序
|
53
|
-
}
|
25
|
+
name_map = {
|
26
|
+
"twse_stats": "cash_flow",
|
27
|
+
"us_stats": "cash_flow"
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
chart_name = name_map.get(self.collection_name, "cash_flow")
|
32
|
+
|
33
|
+
append_pipeline = [
|
34
|
+
{
|
35
|
+
"$unwind": "$seasonal_data" # 展開 seasonal_data 陣列
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"$project": {
|
39
|
+
"_id": 0,
|
40
|
+
"ticker": 1,
|
41
|
+
"company_name": 1,
|
42
|
+
"year": "$seasonal_data.year",
|
43
|
+
"season": "$seasonal_data.season",
|
44
|
+
"cash_flow": {
|
45
|
+
"$ifNull": [f"$seasonal_data.{chart_name}", []]
|
46
|
+
} # 避免 null
|
47
|
+
}
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"$sort": {
|
51
|
+
"year": -1,
|
52
|
+
"season": -1
|
54
53
|
}
|
55
54
|
}
|
56
|
-
|
55
|
+
]
|
56
|
+
|
57
|
+
pipeline = pipeline + append_pipeline
|
57
58
|
|
58
59
|
return pipeline
|
59
60
|
|
60
|
-
def collect_data(self
|
61
|
-
|
62
|
-
|
63
|
-
fetched_data = self.collection.aggregate(pipeline)
|
64
|
-
|
65
|
-
return list(fetched_data)[0]
|
61
|
+
def collect_data(self):
|
62
|
+
return super().collect_data()
|
66
63
|
|
67
64
|
def query_data(self):
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
except:
|
74
|
-
today = StatsDateTime.get_today()
|
75
|
-
target_season = today.season - 1 if (today.season > 1) else 4
|
76
|
-
|
77
|
-
fetched_data = self.collect_data(target_season)
|
78
|
-
|
79
|
-
return self.process_data(fetched_data, target_season)
|
65
|
+
fetched_data = self.collect_data()
|
66
|
+
|
67
|
+
process_fn = self.process_function_map.get(self.collection_name, self.process_data_us)
|
68
|
+
return process_fn(fetched_data)
|
80
69
|
|
81
|
-
def
|
70
|
+
def process_data_twse(self, fetched_data):
|
82
71
|
"""
|
83
72
|
處理現金流量表頁面的所有表格
|
84
73
|
金流表本身沒有比例 但是Ifa有算,
|
85
74
|
項目所屬的情況也不一(分別所屬營業,投資,籌資三個活動)
|
86
75
|
所以這裡選擇不用slicing處理
|
87
76
|
"""
|
88
|
-
cash_flows = fetched_data['cash_flows']
|
89
77
|
|
90
78
|
index_names = []
|
91
79
|
column_names = []
|
@@ -95,15 +83,7 @@ class CashFlowFetcher(StatsFetcher):
|
|
95
83
|
CASHI_dict = dict()
|
96
84
|
CASHF_dict = dict()
|
97
85
|
|
98
|
-
|
99
|
-
"ticker": fetched_data['ticker'],
|
100
|
-
"company_name": fetched_data['company_name'],
|
101
|
-
"cash_flow": dict(),
|
102
|
-
"CASHO": dict(),
|
103
|
-
"CASHI": dict(),
|
104
|
-
"CASHF": dict()
|
105
|
-
}
|
106
|
-
|
86
|
+
# 處理cash_flow 比例
|
107
87
|
checkpoints = ["營業活動之現金流量-間接法", "投資活動之現金流量", "籌資活動之現金流量", "匯率變動對現金及約當現金之影響"]
|
108
88
|
main_cash_flows = [
|
109
89
|
"營業活動之淨現金流入(流出)", "投資活動之淨現金流入(流出)", "籌資活動之淨現金流入(流出)", None
|
@@ -112,67 +92,61 @@ class CashFlowFetcher(StatsFetcher):
|
|
112
92
|
|
113
93
|
# 作法: dictionary中也有checkpoints,如果出現了就換下一個index去計算
|
114
94
|
|
115
|
-
for data in
|
116
|
-
year = data['year']
|
95
|
+
for data in fetched_data:
|
96
|
+
year, season, cash_flow = data['year'], data['season'], data['cash_flow']
|
117
97
|
|
118
|
-
time_index = f"{year}Q{
|
98
|
+
time_index = f"{year}Q{season}"
|
119
99
|
|
120
|
-
cash_flow = data['cash_flow']
|
121
100
|
main_cash_flow_name = None
|
122
101
|
partial_cash_flow = None
|
123
102
|
next_checkpoint = 0
|
124
103
|
|
125
|
-
|
104
|
+
temp_dict = {}
|
105
|
+
|
106
|
+
for index_name, cash_flow_value in cash_flow.items():
|
126
107
|
if (next_checkpoint < 3
|
127
108
|
and index_name == checkpoints[next_checkpoint]): # 找到了主要的變動點
|
128
109
|
main_cash_flow_name = main_cash_flows[next_checkpoint]
|
129
110
|
partial_cash_flow = partial_cash_flows[next_checkpoint]
|
111
|
+
partial_cash_flow[time_index] = {}
|
130
112
|
next_checkpoint += 1
|
113
|
+
|
114
|
+
if (isinstance(cash_flow_value, dict)):
|
115
|
+
value = cash_flow_value.get('value', None)
|
116
|
+
else:
|
117
|
+
value = cash_flow_value
|
118
|
+
|
119
|
+
|
120
|
+
main_value = cash_flow.get(main_cash_flow_name, None)
|
121
|
+
if (isinstance(main_value, dict)):
|
122
|
+
main_value = main_value.get('value', None)
|
123
|
+
else:
|
124
|
+
pass
|
125
|
+
|
131
126
|
try:
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
(value['value'] / cash_flow[
|
137
|
-
main_cash_flow_name]['value']) * 100, 2)
|
138
|
-
table_dict[time_index][index_name][
|
139
|
-
'percentage'] = f"{ratio}%"
|
140
|
-
else:
|
141
|
-
table_dict[time_index][index_name][
|
142
|
-
'percentage'] = None
|
143
|
-
except: # 新增index再做一次
|
144
|
-
if (time_index not in table_dict.keys()):
|
145
|
-
table_dict[time_index] = dict()
|
146
|
-
table_dict[time_index][index_name] = dict()
|
147
|
-
|
148
|
-
table_dict[time_index][index_name]['value'] = value[
|
149
|
-
'value']
|
150
|
-
if (value['value']):
|
151
|
-
ratio = np.round(
|
152
|
-
(value['value'] / cash_flow[
|
153
|
-
main_cash_flow_name]['value']) * 100, 2)
|
154
|
-
table_dict[time_index][index_name][
|
155
|
-
'percentage'] = f"{ratio}%"
|
156
|
-
else:
|
157
|
-
table_dict[time_index][index_name][
|
158
|
-
'percentage'] = None
|
159
|
-
table_dict[time_index][index_name]['value'] = StatsProcessor.cal_non_percentage(value['value'], postfix="千元")
|
160
|
-
try:
|
161
|
-
partial_cash_flow[time_index][index_name] = table_dict[
|
162
|
-
time_index][index_name]
|
127
|
+
ratio = np.round(
|
128
|
+
(value / main_value) * 100, 2
|
129
|
+
)
|
130
|
+
ratio = f"{ratio}%"
|
163
131
|
except:
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
132
|
+
ratio = None
|
133
|
+
|
134
|
+
value = StatsProcessor.cal_non_percentage(value, postfix="千元")
|
135
|
+
temp_dict[index_name] = {
|
136
|
+
"value" : value,
|
137
|
+
"percentage": ratio
|
138
|
+
}
|
139
|
+
|
140
|
+
partial_cash_flow[time_index][index_name] = temp_dict[index_name]
|
168
141
|
|
142
|
+
table_dict[time_index] = temp_dict
|
169
143
|
index_names += list(cash_flow.keys())
|
170
144
|
|
171
145
|
# 轉成dictionary keys
|
172
146
|
index_names = list(dict.fromkeys(index_names))
|
173
147
|
|
174
148
|
cash_flow_table = pd.DataFrame(table_dict)
|
175
|
-
|
149
|
+
cash_flow_table_stats = StatsProcessor.expand_value_percentage(cash_flow_table)
|
176
150
|
|
177
151
|
CASHO_table = pd.DataFrame(CASHO_dict)
|
178
152
|
CASHO_table = StatsProcessor.expand_value_percentage(CASHO_table)
|
@@ -183,9 +157,44 @@ class CashFlowFetcher(StatsFetcher):
|
|
183
157
|
CASHF_table = pd.DataFrame(CASHF_dict)
|
184
158
|
CASHF_table = StatsProcessor.expand_value_percentage(CASHF_table)
|
185
159
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
160
|
+
for time_index in table_dict.keys():
|
161
|
+
table_dict[time_index] = self.flatten_dict(table_dict[time_index], index_names, target_keys=['value', 'percentage'])
|
162
|
+
cash_flow_flatten = pd.DataFrame.from_dict(table_dict)
|
163
|
+
|
164
|
+
target_season = fetched_data[0]['season']
|
165
|
+
target_season_column = cash_flow_flatten.columns.str.endswith(f"Q{target_season}")
|
166
|
+
|
167
|
+
return_dict = {
|
168
|
+
"ticker": self.ticker,
|
169
|
+
"company_name": fetched_data[-1]['company_name'],
|
170
|
+
"cash_flow": cash_flow_table_stats,
|
171
|
+
"CASHO": CASHO_table,
|
172
|
+
"CASHI": CASHI_table,
|
173
|
+
"CASHF": CASHF_table,
|
174
|
+
"cash_flow_all": cash_flow_flatten,
|
175
|
+
"cash_flow_YoY": cash_flow_flatten.loc[:, target_season_column]
|
176
|
+
}
|
177
|
+
return return_dict
|
178
|
+
|
179
|
+
def process_data_us(self, fetched_data):
|
180
|
+
|
181
|
+
table_dict = {
|
182
|
+
f"{data['year']}Q{data['season']}": data['cash_flow']
|
183
|
+
for data in fetched_data
|
184
|
+
}
|
185
|
+
|
186
|
+
cash_flow_df = pd.DataFrame.from_dict(table_dict)
|
190
187
|
|
188
|
+
latest_season = fetched_data[0]['season']
|
189
|
+
target_season_columns = cash_flow_df.columns.str.endswith(
|
190
|
+
f"Q{latest_season}"
|
191
|
+
)
|
192
|
+
cash_flow_df_YoY = cash_flow_df.loc[:, target_season_columns]
|
193
|
+
|
194
|
+
return_dict = {
|
195
|
+
"ticker": self.ticker,
|
196
|
+
"company_name": fetched_data[-1]['company_name'],
|
197
|
+
"cash_flow": cash_flow_df,
|
198
|
+
"cash_flow_YoY": cash_flow_df_YoY
|
199
|
+
}
|
191
200
|
return return_dict
|
@@ -16,9 +16,9 @@ class FinanceOverviewFetcher(StatsFetcher):
|
|
16
16
|
super().__init__(ticker, db_client)
|
17
17
|
|
18
18
|
self.target_fields = StatsProcessor.load_yaml(
|
19
|
-
"finance_overview_dict.yaml")
|
19
|
+
"twse/finance_overview_dict.yaml")
|
20
20
|
self.inverse_dict = StatsProcessor.load_txt(
|
21
|
-
"seasonal_data_field_dict.txt", json_load=True)
|
21
|
+
"twse/seasonal_data_field_dict.txt", json_load=True)
|
22
22
|
|
23
23
|
def prepare_query(self, target_year, target_season):
|
24
24
|
|
@@ -149,7 +149,7 @@ class MonthRevenueFetcher(StatsFetcher):
|
|
149
149
|
try:
|
150
150
|
year = data['year'] - 1
|
151
151
|
total = grand_total_dict[year][12]
|
152
|
-
accum_YoY = round((data['grand_total'] / total) * 100, 2)
|
152
|
+
accum_YoY = round(((data['grand_total'] - total) / total) * 100, 2)
|
153
153
|
accum_YoYs.append(f"{accum_YoY}%")
|
154
154
|
except Exception as e:
|
155
155
|
accum_YoYs.append(None)
|
@@ -3,11 +3,10 @@ import importlib.resources as pkg_resources
|
|
3
3
|
import json
|
4
4
|
import numpy as np
|
5
5
|
import pandas as pd
|
6
|
-
from ..utils import StatsDateTime, StatsProcessor
|
6
|
+
from ..utils import StatsDateTime, StatsProcessor, YoY_Calculator
|
7
7
|
import yaml
|
8
8
|
|
9
9
|
|
10
|
-
|
11
10
|
class ProfitLoseFetcher(StatsFetcher):
|
12
11
|
"""
|
13
12
|
iFa.ai: 財務分析 -> 損益表
|
@@ -16,143 +15,219 @@ class ProfitLoseFetcher(StatsFetcher):
|
|
16
15
|
def __init__(self, ticker, db_client):
|
17
16
|
super().__init__(ticker, db_client)
|
18
17
|
|
19
|
-
self.table_settings = StatsProcessor.load_yaml("profit_lose.yaml")
|
18
|
+
self.table_settings = StatsProcessor.load_yaml("twse/profit_lose.yaml")
|
19
|
+
|
20
|
+
self.process_function_map = {
|
21
|
+
"twse_stats": self.process_data_twse,
|
22
|
+
"us_stats": self.process_data_us
|
23
|
+
}
|
20
24
|
|
21
|
-
def prepare_query(self
|
25
|
+
def prepare_query(self):
|
22
26
|
pipeline = super().prepare_query()
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
"$$target_season_data.season",
|
49
|
-
"profit_lose":
|
50
|
-
"$$target_season_data.profit_lose"
|
51
|
-
}
|
52
|
-
}
|
53
|
-
},
|
54
|
-
"sortBy": {
|
55
|
-
"year": -1
|
56
|
-
} # 按 year 降序排序
|
57
|
-
}
|
28
|
+
name_map = {"twse_stats": "profit_lose", "us_stats": "income_statement"}
|
29
|
+
|
30
|
+
chart_name = name_map.get(self.collection_name, "income_statement")
|
31
|
+
|
32
|
+
append_pipeline = [
|
33
|
+
{
|
34
|
+
"$unwind": "$seasonal_data" # 展開 seasonal_data 陣列
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"$project": {
|
38
|
+
"_id": 0,
|
39
|
+
"ticker": 1,
|
40
|
+
"company_name": 1,
|
41
|
+
"year": "$seasonal_data.year",
|
42
|
+
"season": "$seasonal_data.season",
|
43
|
+
"profit_lose": {
|
44
|
+
"$ifNull": [f"$seasonal_data.{chart_name}", []]
|
45
|
+
} # 避免 null
|
46
|
+
}
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"$sort": {
|
50
|
+
"year": -1,
|
51
|
+
"season": -1
|
58
52
|
}
|
59
53
|
}
|
60
|
-
|
61
|
-
|
62
|
-
return pipeline
|
54
|
+
]
|
63
55
|
|
64
|
-
|
65
|
-
pipeline = self.prepare_query(target_season)
|
56
|
+
pipeline = pipeline + append_pipeline
|
66
57
|
|
67
|
-
|
58
|
+
return pipeline
|
68
59
|
|
69
|
-
|
60
|
+
def collect_data(self):
|
61
|
+
return super().collect_data()
|
70
62
|
|
71
63
|
def query_data(self):
|
72
|
-
|
73
|
-
latest_time = StatsDateTime.get_latest_time(
|
74
|
-
self.ticker, self.collection)['last_update_time']
|
75
|
-
target_season = latest_time['seasonal_data']['latest_season']
|
76
|
-
except Exception as e:
|
77
|
-
today = StatsDateTime.get_today()
|
78
|
-
|
79
|
-
target_season = today.season
|
80
|
-
target_season = target_season - 1 if target_season > 1 else 4
|
81
|
-
|
82
|
-
fetched_data = self.collect_data(target_season)
|
64
|
+
fetched_data = self.collect_data()
|
83
65
|
|
84
|
-
|
66
|
+
process_fn = self.process_function_map.get(
|
67
|
+
self.collection_name, self.process_data_us
|
68
|
+
)
|
69
|
+
return process_fn(fetched_data)
|
85
70
|
|
86
|
-
def
|
71
|
+
def process_data_twse(self, fetched_data):
|
87
72
|
|
88
|
-
|
73
|
+
latest_time = StatsDateTime.get_latest_time(
|
74
|
+
self.ticker, self.collection
|
75
|
+
).get('last_update_time', {})
|
89
76
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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(
|
83
|
+
'seasonal_data',{}
|
84
|
+
).get('latest_season', 4)
|
94
85
|
|
95
86
|
return_dict = {
|
96
|
-
"ticker":
|
97
|
-
"company_name": fetched_data['company_name'],
|
87
|
+
"ticker": self.ticker,
|
88
|
+
"company_name": fetched_data[-1]['company_name'],
|
98
89
|
}
|
99
90
|
|
100
|
-
|
101
|
-
year
|
102
|
-
|
103
|
-
|
91
|
+
profit_lose_dict = {
|
92
|
+
f"{data['year']}Q{data['season']}": data['profit_lose']
|
93
|
+
for data in fetched_data
|
94
|
+
}
|
104
95
|
|
96
|
+
profit_lose_df = pd.DataFrame.from_dict(profit_lose_dict)
|
97
|
+
target_season_col = profit_lose_df.columns.str.endswith(
|
98
|
+
f"Q{target_season}"
|
99
|
+
)
|
100
|
+
profit_lose_df = profit_lose_df.loc[:, target_season_col]
|
101
|
+
profit_lose_df = StatsProcessor.expand_value_percentage(
|
102
|
+
profit_lose_df
|
103
|
+
)
|
104
|
+
|
105
|
+
value_col = profit_lose_df.columns.str.endswith(f"_value")
|
106
|
+
percentage_col = profit_lose_df.columns.str.endswith(f"_percentage")
|
107
|
+
|
108
|
+
grand_total_value_col = profit_lose_df.columns.str.endswith(
|
109
|
+
f"grand_total_value"
|
110
|
+
)
|
111
|
+
grand_total_percentage_col = profit_lose_df.columns.str.endswith(
|
112
|
+
f"grand_total_percentage"
|
113
|
+
)
|
114
|
+
|
115
|
+
profit_lose_stats_df = profit_lose_df.loc[:, (
|
116
|
+
(value_col & ~grand_total_value_col) |
|
117
|
+
(percentage_col & ~grand_total_percentage_col)
|
118
|
+
)]
|
119
|
+
|
120
|
+
for time_index, profit_lose in profit_lose_dict.items():
|
105
121
|
# 蒐集整體的keys
|
106
|
-
index_names
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
122
|
+
index_names = list(profit_lose.keys())
|
123
|
+
target_keys = [
|
124
|
+
"value",
|
125
|
+
"percentage",
|
126
|
+
"grand_total",
|
127
|
+
"grand_total_percentage",
|
128
|
+
"YoY_1",
|
129
|
+
"YoY_3",
|
130
|
+
"YoY_5",
|
131
|
+
"YoY_10",
|
132
|
+
"grand_total_YoY_1",
|
133
|
+
"grand_total_YoY_3",
|
134
|
+
"grand_total_YoY_5",
|
135
|
+
"grand_total_YoY_10",
|
136
|
+
]
|
137
|
+
# flatten dict
|
138
|
+
new_profit_lose = self.flatten_dict(
|
139
|
+
profit_lose, index_names, target_keys
|
140
|
+
)
|
141
|
+
profit_lose_dict[time_index] = new_profit_lose
|
142
|
+
|
143
|
+
profit_lose_df = pd.DataFrame.from_dict(profit_lose_dict)
|
144
|
+
|
145
|
+
# EPS的value用元計算
|
146
|
+
eps_index = profit_lose_df.index.str.endswith(
|
147
|
+
"_value"
|
148
|
+
) & profit_lose_df.index.str.contains("每股盈餘")
|
149
|
+
profit_lose_df.loc[eps_index] = profit_lose_df.loc[
|
150
|
+
eps_index].apply(
|
151
|
+
lambda x: StatsProcessor.cal_non_percentage(x, postfix="元")
|
152
|
+
)
|
153
|
+
|
154
|
+
# percentage處理
|
155
|
+
percentage_index = profit_lose_df.index.str.endswith("percentage")
|
156
|
+
profit_lose_df.loc[percentage_index] = profit_lose_df.loc[
|
157
|
+
percentage_index].apply(
|
158
|
+
lambda x: StatsProcessor.
|
159
|
+
cal_non_percentage(x, to_str=True, postfix="%")
|
160
|
+
)
|
161
|
+
|
162
|
+
# YoY處理: 乘以100
|
163
|
+
YoY_index = profit_lose_df.index.str.contains("YoY")
|
164
|
+
profit_lose_df.loc[YoY_index] = profit_lose_df.loc[
|
165
|
+
YoY_index].apply(lambda x: StatsProcessor.cal_percentage(x))
|
166
|
+
|
167
|
+
# 剩下的處理: 乘以千元
|
168
|
+
value_index = ~(
|
169
|
+
percentage_index | YoY_index | profit_lose_df.index.isin(eps_index)
|
170
|
+
) # 除了上述以外的 index
|
171
|
+
profit_lose_df.loc[value_index] = profit_lose_df.loc[
|
172
|
+
value_index].apply(
|
173
|
+
lambda x: StatsProcessor.cal_non_percentage(x, postfix="千元")
|
174
|
+
)
|
175
|
+
|
176
|
+
total_table = profit_lose_df.replace("N/A", None)
|
177
|
+
|
178
|
+
# 取特定季度
|
179
|
+
target_season_columns = total_table.columns.str.endswith(
|
180
|
+
f"Q{target_season}"
|
181
|
+
)
|
182
|
+
total_table_YoY = total_table.loc[:, target_season_columns]
|
138
183
|
|
139
184
|
for name, setting in self.table_settings.items():
|
140
|
-
|
141
|
-
target_indexes = [target.strip() for target in setting['target_index']]
|
142
|
-
else:
|
143
|
-
target_indexes = [None]
|
144
|
-
|
185
|
+
target_indexes = setting.get('target_index', [None])
|
145
186
|
for target_index in target_indexes:
|
146
187
|
try:
|
147
|
-
return_dict[name] = StatsProcessor.
|
148
|
-
total_table=
|
188
|
+
return_dict[name] = StatsProcessor.slice_table(
|
189
|
+
total_table=total_table_YoY,
|
149
190
|
mode=setting['mode'],
|
150
|
-
target_index=target_index
|
191
|
+
target_index=target_index
|
192
|
+
)
|
151
193
|
break
|
152
194
|
except Exception as e:
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
195
|
+
print(str(e))
|
196
|
+
continue
|
197
|
+
|
198
|
+
return_dict.update(
|
199
|
+
{
|
200
|
+
"profit_lose": profit_lose_stats_df,
|
201
|
+
"profit_lose_all": total_table.copy(),
|
202
|
+
"profit_lose_YoY": total_table_YoY
|
203
|
+
}
|
204
|
+
)
|
205
|
+
return return_dict
|
206
|
+
|
207
|
+
def process_data_us(self, fetched_data):
|
208
|
+
|
209
|
+
table_dict = {
|
210
|
+
f"{data['year']}Q{data['season']}": data['profit_lose']
|
211
|
+
for data in fetched_data
|
212
|
+
}
|
213
|
+
|
214
|
+
table_dict = YoY_Calculator.cal_QoQ(table_dict)
|
215
|
+
table_dict = YoY_Calculator.cal_YoY(table_dict)
|
216
|
+
|
217
|
+
for time_index, data_dict in table_dict.items():
|
218
|
+
table_dict[time_index] = self.flatten_dict(
|
219
|
+
value_dict=data_dict,
|
220
|
+
indexes=list(data_dict.keys()),
|
221
|
+
target_keys=["value", "growth"] +
|
222
|
+
[f"YoY_{i}" for i in [1, 3, 5, 10]]
|
223
|
+
)
|
224
|
+
|
225
|
+
# 計算QoQ
|
226
|
+
|
227
|
+
return_dict = {
|
228
|
+
"ticker": self.ticker,
|
229
|
+
"company_name": fetched_data[-1]['company_name'],
|
230
|
+
"profit_lose": pd.DataFrame.from_dict(table_dict)
|
231
|
+
}
|
157
232
|
|
158
233
|
return return_dict
|