-
Notifications
You must be signed in to change notification settings - Fork 2
/
ffxiv_db_constructor.py
369 lines (322 loc) · 14 KB
/
ffxiv_db_constructor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
"""
Module to perform initial database creation/population for FFXIV-Market-Calculator
"""
import os
import pathlib
import sys
import requests
from sql_helpers import SqlManager
def filter_marketable_items(items, marketable_ids):
"""
Filters items to only marketable items
Parameters:
items : list
Raw item data from api
marketable_ids : list
Marketable item IDs
"""
marketable_items = [None, (
'item_num', 'name', 'ave_cost', 'regular_sale_velocity', 'ave_nq_cost', 'nq_sale_velocity',
'ave_hq_cost', 'hq_sale_velocity', 'gatherable'), (
'INTEGER PRIMARY KEY', 'TEXT', 'INTEGER', 'REAL', 'INTEGER', 'REAL',
'INTEGER', 'REAL', 'TEXT DEFAULT "False" NOT NULL')]
line_concatenate = []
for line in items[3:]:
split_line = line_concatenate + line.split(',')
if len(split_line) >= 98:
line_concatenate = []
if split_line[0] in marketable_ids:
marketable_items.append((split_line[0], split_line[len(split_line) - 88],
0, 0, 0, 0, 0, 0, 'False'))
else:
line_concatenate = split_line
return marketable_items
class FfxivDbCreation:
"""
Class for handling the construction of databases for the script.
Attributes:
-------
database : SqlManager object
SqlManager object for database operations
Methods:
-------
market_db_create():
Creates a new database for market data
global_db_create():
Creates a new database for global data
get_data_from_url():
Retrieves data from web api
get_marketable_ids():
Retrieves data for marketable items
filter_marketable_recipes():
Filters recipes to marketable items
filter_datacentres():
Filters for usable datacentres
filter_worlds():
Filters for usable worlds
base_state_table():
Creates the base templated state table
csv_to_db():
Handles writing all data from other methods to the databases
"""
def __init__(self, db_name):
"""
pulls data from the universalis API to limit items to marketable items
pulls data from:
https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/...
Item.csv to get item data
Recipe.csv to get recipe data
WorldDCGroupType.csv to get dc data
World.csv to get world data
creates a new database w/ the given name if it doesn't exist
Parameters:
db_name : str
The name that the database should be called
"""
if os.path.exists(pathlib.Path(__file__).parent / db_name):
raise ValueError("Database with that name already exists")
self.database = SqlManager(db_name)
if db_name == os.path.join("databases", "global_db"):
self.global_db_create()
else:
self.market_db_create()
def market_db_create(self):
"""
Creates the blank market data database
"""
# gets a list of integers in string format ['2','3','5','6',...]
marketable_ids = self.get_marketable_ids()
print('Got marketable ID list')
# gets a list of tuples containing the item data
# index 0-2 are the column numbers, titles, and data types; index 3+ is the data
items = self.get_data_from_url(
'https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Item.csv'
)
print('Got raw item CSV')
# gets a list of tuples containing the item data
# index 0-2 are the column numbers, titles, and data types; index 3+ is the data
recipes = self.get_data_from_url(
'https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Recipe.csv')
recipes[1] = recipes[1].replace('#', 'CSVkey')
print('Got raw recipe CSV')
marketable_items = filter_marketable_items(items, marketable_ids)
print('item CSV filtered to only marketable')
marketable_recipes = self.filter_marketable_recipes(recipes, marketable_ids)
print('recipes CSV filtered to only marketable')
self.csv_to_db(marketable_items, 'item')
print('item table created')
self.add_gatherable()
print("gatherable flags added to item table")
self.csv_to_db(marketable_recipes, 'recipe')
print('recipe table created')
for i in range(10):
self.database.execute_query(
f"ALTER TABLE recipe ADD ingredient_cost_{i} INTEGER DEFAULT 9999999;"
)
self.database.execute_query(
"ALTER TABLE recipe ADD cost_to_craft GENERATED ALWAYS AS ("
"amount_ingredient_0 * ingredient_cost_0 + amount_ingredient_1 * ingredient_cost_1 + "
"amount_ingredient_2 * ingredient_cost_2 + amount_ingredient_3 * ingredient_cost_3 + "
"amount_ingredient_4 * ingredient_cost_4 + amount_ingredient_5 * ingredient_cost_5 + "
"amount_ingredient_6 * ingredient_cost_6 + amount_ingredient_7 * ingredient_cost_7 + "
"amount_ingredient_8 * ingredient_cost_8 + amount_ingredient_9 * ingredient_cost_9)")
self.database.execute_query("ALTER TABLE item ADD cost_to_craft INTEGER DEFAULT 0;")
self.database.execute_query(
"ALTER TABLE item ADD craft_profit GENERATED ALWAYS AS "
"(CASE cost_to_craft WHEN 0 THEN 0 ELSE ave_cost - cost_to_craft END);"
)
self.database.execute_query(
"ALTER TABLE item ADD craft_profit_per_day GENERATED ALWAYS AS "
"(craft_profit * regular_sale_velocity);"
)
self.database.execute_query(
"ALTER TABLE item ADD raw_profit_per_day GENERATED ALWAYS AS "
"(ave_cost * regular_sale_velocity);"
)
def global_db_create(self):
"""
Creates the blank global data database
"""
# gets a list of tuples containing the datacentre data
datacentres = self.get_data_from_url(
'https://raw.githubusercontent.com/xivapi/'
'ffxiv-datamining/master/csv/WorldDCGroupType.csv'
)
datacentres[1] = datacentres[1].replace('#', 'dc_key')
print('Got raw datacentre CSV')
# gets a list of tuples containing the world data
worlds = self.get_data_from_url(
'https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/World.csv')
worlds[1] = worlds[1].replace('#', 'world_key')
print('got raw world CSV')
usable_datacentres = self.filter_datacentres(datacentres)
print('datacentres CSV filtered to only usable')
usable_worlds = self.filter_worlds(worlds)
print('worlds CSV filtered to only usable')
state_table = self.base_state_table()
print('base state table data')
self.csv_to_db(usable_datacentres, 'datacentre')
print('datacentre table created')
self.csv_to_db(usable_worlds, 'world')
print('world table created')
self.csv_to_db(state_table, 'state')
@staticmethod
def get_data_from_url(url):
"""
pulls data from web apis
Parameters:
url : str
url to perform the http request on
"""
request_data = requests.get(url)
if request_data.status_code == 200:
data = request_data.content.decode('utf-8-sig')
return data.splitlines()
sys.exit(f"{url} not available")
def get_marketable_ids(self):
"""
Retrieves data for marketable items
"""
marketable_id_data = self.get_data_from_url("https://universalis.app/api/marketable")
marketable_ids = marketable_id_data[0].split(',')
marketable_ids[0] = marketable_ids[0].replace('[', '')
marketable_ids[-1] = marketable_ids[-1].replace(']', '')
return marketable_ids
@staticmethod
def filter_marketable_recipes(recipes, marketable_ids):
"""
Filters recipes to marketable items
Parameters:
recipes : list
Raw recipe data from api
marketable_ids : list
List of marketable item IDs
"""
marketable_recipes = recipes[0:3]
marketable_recipes[
1] = 'csv_key,number,craft_type,recipe_level_table,item_result,amount_result,' \
'item_ingredient_0,amount_ingredient_0,item_ingredient_1,amount_ingredient_1,' \
'item_ingredient_2,amount_ingredient_2,item_ingredient_3,amount_ingredient_3,' \
'item_ingredient_4,amount_ingredient_4,item_ingredient_5,amount_ingredient_5,' \
'item_ingredient_6,amount_ingredient_6,item_ingredient_7,amount_ingredient_7,' \
'item_ingredient_8,amount_ingredient_8,item_ingredient_9,amount_ingredient_9'
marketable_recipes[
2] = 'INTEGER PRIMARY KEY,INTEGER,INTEGER,INTEGER,INTEGER,INTEGER,' \
'INTEGER,INTEGER,INTEGER,INTEGER,' \
'INTEGER,INTEGER,INTEGER,INTEGER,' \
'INTEGER,INTEGER,INTEGER,INTEGER,' \
'INTEGER,INTEGER,INTEGER,INTEGER,' \
'INTEGER,INTEGER,INTEGER,INTEGER'
marketable_recipes[1] = tuple(marketable_recipes[1].split(','))
marketable_recipes[2] = tuple(marketable_recipes[2].split(','))
for line in recipes[3:]:
split_line = line.split(',')
crafted_item_id = split_line[4]
if crafted_item_id in marketable_ids:
marketable_recipes.append(tuple(split_line[:26]))
return marketable_recipes
@staticmethod
def filter_datacentres(datacentres):
"""
Filters for usable datacentres
Parameters:
datacentres : list
Raw datacentre data from api
"""
usable_datacentres = datacentres[0:3]
usable_datacentres[1] = 'dc_key,name,region'
usable_datacentres[2] = 'INTEGER PRIMARY KEY, STRING, INTEGER'
usable_datacentres[1] = tuple(usable_datacentres[1].split(','))
usable_datacentres[2] = tuple(usable_datacentres[2].split(','))
for line in datacentres[3:]:
split_line = line.split(',')
datacentre_id = int(split_line[0])
unquoted_line = []
for item in split_line:
unquoted_line.append(item.replace('"', ''))
if 1 <= datacentre_id < 99:
usable_datacentres.append(tuple(unquoted_line))
return usable_datacentres
@staticmethod
def filter_worlds(worlds):
"""
Filters for usable worlds
Parameters:
worlds : list
Raw world data from api
"""
usable_worlds = worlds[0:3]
usable_worlds[1] = 'world_key, name, datacenter'
usable_worlds[2] = 'INTEGER PRIMARY KEY, STRING, INTEGER'
usable_worlds[1] = tuple(usable_worlds[1].split(','))
usable_worlds[2] = tuple(usable_worlds[2].split(','))
for line in worlds[3:]:
split_line = line.split(',')
world_id = int(split_line[0])
is_public = split_line[-1]
if is_public == 'True' and world_id != 38:
split_line = [split_line[0], split_line[2].replace('"', ''), split_line[5]]
usable_worlds.append(tuple(split_line))
return usable_worlds
@staticmethod
def base_state_table():
"""
Creates the base templated state table
"""
state = ['key,0,1',
'marketboard_type,location,last_id',
'STRING,STRING NOT NULL UNIQUE,INTEGER',
'World,Zurvan,0']
state[0] = tuple(state[0].split(","))
state[1] = tuple(state[1].split(','))
state[2] = tuple(state[2].split(','))
state[3] = tuple(state[3].split(','))
return state
def add_gatherable(self):
"""
Marks gatherable items in item table
"""
gatherable_id_data = self.get_data_from_url("https://raw.githubusercontent.com"
"/xivapi/ffxiv-datamining/master/csv/"
"GatheringItem.csv")
gatherable_items = []
for item in gatherable_id_data[3:]:
item_data = item.split(",")
if item_data[3] == "True":
gatherable_items.append(tuple((item_data[1],)))
self.database.execute_query_many(
"UPDATE item SET gatherable = 'True' WHERE item_num = ?", gatherable_items)
# function used to convert original files into the DB
def csv_to_db(self, csv_data, table_name):
"""
Filters for usable worlds
Parameters:
csv_data : list
Schema/data for db creation/population
table_name : string
Name of the table to be created in the database
"""
database = self.database
column_names = csv_data[1]
data_types = csv_data[2]
num_columns = len(column_names)
question_marks = ""
for _ in column_names:
question_marks = question_marks + '?,'
question_marks = question_marks[:-1]
for column, name in enumerate(column_names):
if name == "":
column_names[column] = f'unnamedColumn{column}'
# create a new table
creation_command = f"CREATE TABLE IF NOT EXISTS {table_name} ("
for i in range(num_columns):
creation_command = creation_command + f"{column_names[i]} {data_types[i]}, "
if table_name == "recipe":
creation_command = creation_command + \
"FOREIGN KEY (item_result) REFERENCES item (item_num))"
else:
creation_command = creation_command[:-2] + ")"
database.execute_query(creation_command)
# insert all values
insert_command = f"INSERT INTO {table_name} VALUES ({question_marks})"
database.execute_query_many(insert_command, csv_data[3:])