diff --git a/app.py b/app.py index 653523a..4c1b229 100644 --- a/app.py +++ b/app.py @@ -31,12 +31,13 @@ ] SOURCES = [ "https://github.com/CSSEGISandData/COVID-19", + "https://github.com/govex/COVID-19", "https://www.worldometers.info/coronavirus/" ] route_homepage = { "documentation": f"{BASE_PATH}/doc", "routes": ROUTES, - "": "confirmed | recovered | deaths", + "": "confirmed | recovered | deaths | vaccinated", "Api version": API_VERSION, "discord": "https://discord.gg/wTxbQYb", "sources": SOURCES, @@ -52,6 +53,8 @@ app = Flask(__name__) app.url_map.strict_slashes = False +# Flask orders the dates wrong for some reason ?!?! +app.config['JSON_SORT_KEYS'] = False limiter = Limiter( app, get_remote_address, @@ -431,7 +434,7 @@ def get(self, data_type: int): @api.route(f"/api/{API_VERSION}/history///") class HistoryDataTypeCountry(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`", "country": "Full name or ISO-3166-1"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`", "country": "Full name or ISO-3166-1"}) def get(self, data_type: str, country: str): return history_country(data_type, country) @@ -439,7 +442,7 @@ def get(self, data_type: str, country: str): @api.route(f"/api/{API_VERSION}/history///") class HistoryDataTypeRegion(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`", "country": "Full name or ISO-3166-1", "region": "Region name"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`", "country": "Full name or ISO-3166-1", "region": "Region name"}) def get(self, data_type: str, country: str, region: str): return history_region(data_type, country, region) @@ -447,7 +450,7 @@ def get(self, data_type: str, country: str, region: str): @api.route(f"/api/{API_VERSION}/history///regions") class HistoryDataTypeRegions(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`", "country": "Full name or ISO-3166-1"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`", "country": "Full name or ISO-3166-1"}) def get(self, data_type: str, country: str): return history_region_all(data_type, country) @@ -455,7 +458,7 @@ def get(self, data_type: str, country: str): @api.route(f"/api/{API_VERSION}/history//total") class HistoryDataTypeTotal(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}) def get(self, data_type: str): return history_region_world(data_type) @@ -463,14 +466,14 @@ def get(self, data_type: str): @api.route(f"/api/{API_VERSION}/proportion//") class ProportionDataType(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}) def get(self, data_type: str): return proportion(data_type) @api.route(f"/api/{API_VERSION}/proportion//total") class ProportionDataTypeTotal(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}, + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}, description="Returns the percentage of the world's population to be affected by COVID-19") def get(self, data_type: str): return proportion_region_world(data_type) @@ -478,49 +481,49 @@ def get(self, data_type: str): @api.route(f"/api/{API_VERSION}/proportion///") class ProportionDataTypeCountry(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`", "country": "Full name or ISO-3166-1"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`", "country": "Full name or ISO-3166-1"}) def get(self, data_type: str, country: str): return proportion_country(data_type, country) @api.route(f"/api/{API_VERSION}/daily//") class DailyDataType(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}) def get(self, data_type: str): return daily(data_type) @api.route(f"/api/{API_VERSION}/daily//total") class DailyDataTypeTotal(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}) def get(self, data_type: str): return daily_region_world(data_type) @api.route(f"/api/{API_VERSION}/daily///") class DailyDataTypeCountry(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`", "country": "Full name or ISO-3166-1"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`", "country": "Full name or ISO-3166-1"}) def get(self, data_type: str, country: str): return daily_country(data_type, country) @api.route(f"/api/{API_VERSION}/proportion-daily//") class ProportionDailyDataType(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}) def get(self, data_type: str): return proportion_daily(data_type) @api.route(f"/api/{API_VERSION}/proportion-daily//total") class ProportionDailyDataTypeTotal(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`"}) def get(self, data_type: str): return proportion_daily_region_world(data_type) @api.route(f"/api/{API_VERSION}/proportion-daily//") class ProportionDailyDataTypeCountry(Resource): @api.doc(responses=responses, - params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths`", "country": "Full name or ISO-3166-1"}) + params={"data_type": "Input accepted : `confirmed` | `recovered` | `deaths` | `vaccinated`", "country": "Full name or ISO-3166-1"}) def get(self, data_type: str, country: str): return proportion_daily_country(data_type, country) diff --git a/src/utils.py b/src/utils.py index d283e26..48981ee 100644 --- a/src/utils.py +++ b/src/utils.py @@ -17,6 +17,7 @@ CSV_CONFIRMED = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv" CSV_DEATHS = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv" CSV_RECOVERED = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv" +CSV_VACCINATED = "https://raw.githubusercontent.com/govex/COVID-19/master/data_tables/vaccine_data/global_data/time_series_covid19_vaccine_doses_admin_global.csv" CSV_CONFIRMED_US = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_US.csv" CSV_DEATHS_US = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_US.csv" @@ -27,6 +28,7 @@ CSV_CONFIRMED_FPATH = "csv_confirmed.csv" CSV_DEATHS_FPATH = "csv_deaths.csv" CSV_RECOVERD_FPATH = "csv_recovered.csv" +CSV_VACCINATED_FPATH = "csv_vaccinated.csv" CSV_POPULATIONS = "data/populations.csv" populations = {} @@ -70,14 +72,17 @@ def update(): dl_csv(CSV_CONFIRMED, CSV_CONFIRMED_FPATH) dl_csv(CSV_DEATHS, CSV_DEATHS_FPATH) dl_csv(CSV_RECOVERED, CSV_RECOVERD_FPATH) + dl_csv(CSV_VACCINATED, CSV_VACCINATED_FPATH) dl_csv(CSV_CONFIRMED_US, CSV_CONFIRMED_FPATH_US) dl_csv(CSV_DEATHS_US, CSV_DEATHS_FPATH_US) csv_to_json(CSV_CONFIRMED_FPATH) csv_to_json(CSV_DEATHS_FPATH) csv_to_json(CSV_RECOVERD_FPATH) + csv_to_json(CSV_VACCINATED_FPATH, is_govex=True) region_csv_to_json(CSV_CONFIRMED_FPATH) region_csv_to_json(CSV_DEATHS_FPATH) region_csv_to_json(CSV_RECOVERD_FPATH) + region_csv_to_json(CSV_VACCINATED_FPATH, is_govex=True) region_csv_to_json(CSV_CONFIRMED_FPATH_US, is_us=True) region_csv_to_json(CSV_DEATHS_FPATH_US, is_us=True) store_data() @@ -87,26 +92,42 @@ def dl_csv(csv_type, fpath): with open(fpath, "w+") as f: f.write(r.text) -def csv_to_json(csv_fpath): +def csv_to_json(csv_fpath, is_govex=False): csv_json = {} + + if is_govex: + province_key = "Province_State" + country_key = "Country_Region" + key_start = 12 + date_format = "%Y-%m-%d" + else: + province_key = "Province/State" + country_key = "Country/Region" + key_start = 4 + date_format = "%m/%d/%y" + with open("iso-3166.json", "r") as isof: iso_data = json.load(isof) with open(csv_fpath, "r") as csv_file: data = csv.DictReader(csv_file) for row in data: - if row["Country/Region"] not in csv_json: - if row["Country/Region"] in SPECIAL_CASES: - country = SPECIAL_CASES[row["Country/Region"]]["name"] + if row[country_key] not in csv_json: + if row[country_key] in SPECIAL_CASES: + country = SPECIAL_CASES[row[country_key]]["name"] else: - country = row["Country/Region"] + country = row[country_key] csv_json[country] = {"history": {}} - for key in list(row.keys())[4:]: - new_k = datetime.datetime.strptime(key, "%m/%d/%y").strftime("%m/%d/%y") - csv_json[country]["history"][new_k] = int(row[key]) - else: - for key in list(row.keys())[4:]: - new_k = datetime.datetime.strptime(key, "%m/%d/%y").strftime("%m/%d/%y") - csv_json[country]["history"][new_k] += int(row[key]) + for key in list(row.keys())[key_start:]: + new_k = datetime.datetime.strptime(key, date_format).strftime("%m/%d/%y") + csv_json[country]["history"][new_k] = int(row[key]) if (row[key] != '') else 0 + elif is_govex != True: + for key in list(row.keys())[key_start:]: + new_k = datetime.datetime.strptime(key, date_format).strftime("%m/%d/%y") + csv_json[country]["history"][new_k] += int(row[key]) if (row[key] != '') else 0 + + #print(csv_json[country]["history"]) + #csv_json[country]["history"] = dict(sorted(csv_json[country]["history"].items(), key=lambda p: p[1])) + #print(csv_json[country]["history"]) for k in csv_json.keys(): for iso in iso_data: @@ -124,16 +145,30 @@ def csv_to_json(csv_fpath): with open(csv_fpath.replace(".csv", ".json"), "w+") as f: f.write(str(json.dumps(csv_json))) -def region_csv_to_json(csv_fpath, is_us=False): +def region_csv_to_json(csv_fpath, is_us=False, is_govex=False): csv_json = {} if is_us: province_key = "Province_State" country_key = "Country_Region" key_start = 12 + date_format = "%m/%d/%y" else: province_key = "Province/State" country_key = "Country/Region" key_start = 4 + date_format = "%m/%d/%y" + + if is_govex: + province_key = "Province_State" + country_key = "Country_Region" + key_start = 12 + date_format = "%Y-%m-%d" + elif is_us == False: + province_key = "Province/State" + country_key = "Country/Region" + key_start = 4 + date_format = "%m/%d/%y" + with open("iso-3166.json", "r") as isof: iso_data = json.load(isof) with open(csv_fpath, "r") as csv_file: @@ -150,24 +185,24 @@ def region_csv_to_json(csv_fpath, is_us=False): csv_json[country] = {"regions": {}} province = row[province_key] for key in list(row.keys())[key_start:]: - new_k = datetime.datetime.strptime(key, "%m/%d/%y").strftime("%m/%d/%y") + new_k = datetime.datetime.strptime(key, date_format).strftime("%m/%d/%y") if province not in csv_json[country]["regions"]: csv_json[country]["regions"][province] = { "history": {} } - csv_json[country]["regions"][province]["history"][new_k] = int(float(row[key])) - else: + csv_json[country]["regions"][province]["history"][new_k] = int(float(row[key])) if (row[key] != '') else 0 + elif is_govex != True: province = row[province_key] for key in list(row.keys())[key_start:]: - new_k = datetime.datetime.strptime(key, "%m/%d/%y").strftime("%m/%d/%y") + new_k = datetime.datetime.strptime(key, date_format).strftime("%m/%d/%y") if province not in csv_json[country]["regions"]: csv_json[country]["regions"][province] = { "history": {} } if is_us and csv_json[country]["regions"][province]["history"].get(new_k): - csv_json[country]["regions"][province]["history"][new_k] += int(float(row[key])) + csv_json[country]["regions"][province]["history"][new_k] += int(float(row[key])) if (row[key] != '') else 0 else: - csv_json[country]["regions"][province]["history"][new_k] = int(float(row[key])) + csv_json[country]["regions"][province]["history"][new_k] = int(float(row[key])) if (row[key] != '') else 0 for k in csv_json.keys(): @@ -208,11 +243,16 @@ def replace_null_value(dataset: list): data_c = read_json(CSV_CONFIRMED_FPATH.replace(".csv", ".json")) data_r = read_json(CSV_RECOVERD_FPATH.replace(".csv", ".json")) data_d = read_json(CSV_DEATHS_FPATH.replace(".csv", ".json")) + data_v = read_json(CSV_VACCINATED_FPATH.replace(".csv", ".json")) for kv_replace in dataset: tmp = kv_replace confirmed = find_val_replace_null(tmp["country"], data_c, tmp["totalCases"]) recovered = find_val_replace_null(tmp["country"], data_r, tmp["totalRecovered"]) deaths = find_val_replace_null(tmp["country"], data_d, tmp["totalDeaths"]) + # TODO: find / calculate data for whole world vaccinations + if tmp["country"] in data_v: + tmp["totalVaccinated"] = list(data_v[tmp["country"]]["history"].values())[-1] + if tmp["totalCases"] is None: tmp["totalCases"] = confirmed if tmp["activeCases"] is None: @@ -223,6 +263,7 @@ def replace_null_value(dataset: list): tmp["totalDeaths"] = deaths if tmp["totalRecovered"] is None: tmp["totalRecovered"] = recovered + new_dataset.append(tmp) return new_dataset