Skip to content

Commit ed2dcb0

Browse files
authored
Merge pull request #172 from cmu-delphi/development
Merge latest from development and deploy to staging
2 parents 40bcfb4 + 56e3b8e commit ed2dcb0

File tree

3 files changed

+196
-26
lines changed

3 files changed

+196
-26
lines changed

.github/workflows/build-and-deploy.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ jobs:
99
github.ref == 'refs/heads/main' ||
1010
github.ref == 'refs/heads/staging' ||
1111
github.ref == 'refs/heads/development'
12+
1213
strategy:
1314
matrix:
1415
# Specify the docker-compose services to build images from

Pipfile

+28-26
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,44 @@ verify_ssl = true
44
name = "pypi"
55

66
[packages]
7-
django = "*"
8-
django-filter = "*"
9-
django-extensions = "*"
10-
pillow = "*"
11-
pre-commit = "*"
127
'sentry-sdk[django]' = "*"
13-
python-dotenv = "*"
14-
django-health-check = "*"
15-
django-cors-headers = "*"
16-
django-debug-toolbar = "*"
17-
factory-boy = "*"
18-
linkpreview = "*"
19-
django-redis = "*"
20-
mysqlclient = "*"
21-
django-import-export = "*"
8+
celery = {version = "*", extras = ["redis"]}
229
coverage = "*"
10+
crispy-bootstrap5 = "*"
11+
django = "*"
12+
django-celery-beat = "*"
13+
django-cors-headers = "*"
2314
django-coverage-plugin = "*"
15+
django-crispy-forms = "*"
16+
django-debug-toolbar = "*"
17+
django-docs = "*"
18+
django-extensions = "*"
2419
django-extensions-models = "*"
25-
mypy = "*"
20+
django-filter = "*"
21+
django-health-check = "*"
22+
django-import-export = "*"
23+
django-redis = "*"
2624
django-stubs = "*"
27-
tzdata = "*"
2825
djangorestframework = "*"
29-
pyparsing = "*"
30-
pydot = "*"
31-
markdown = "*"
3226
drf-spectacular = "*"
33-
django-docs = "*"
34-
sphinxcontrib-django = "*"
35-
sphinx-rtd-theme = "*"
36-
django-crispy-forms = "*"
37-
crispy-bootstrap5 = "*"
38-
django-celery-beat = "*"
39-
celery = {version = "*", extras = ["redis"]}
27+
factory-boy = "*"
4028
flower = "*"
4129
gunicorn = "*"
30+
linkpreview = "*"
31+
markdown = "*"
32+
mypy = "*"
33+
mysqlclient = "*"
34+
mysql-connector-python = "*"
35+
pillow = "*"
36+
pre-commit = "*"
37+
pydot = "*"
38+
pyparsing = "*"
39+
python-dotenv = "*"
40+
sphinx-rtd-theme = "*"
41+
sphinxcontrib-django = "*"
42+
sqlalchemy = "*"
4243
types-requests = "*"
44+
tzdata = "*"
4345

4446
[dev-packages]
4547
flake8 = "*"

src/sync-signals-with-metadata.py

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import logging
2+
import os
3+
from datetime import datetime
4+
5+
import requests
6+
import sqlalchemy
7+
from sqlalchemy import create_engine, update
8+
from sqlalchemy.engine import Engine
9+
from sqlalchemy.orm import DeclarativeBase, sessionmaker
10+
11+
logger = logging.getLogger(__name__)
12+
13+
SQLALCHEMY_DATABASE_URI = os.environ.get(
14+
"SQLALCHEMY_DATABASE_URI",
15+
"mysql+mysqlconnector://root:ROOT_PASSWORD@localhost:3306/mysql_database",
16+
)
17+
engine: Engine = create_engine(
18+
SQLALCHEMY_DATABASE_URI, execution_options={"engine_id": "default"}
19+
)
20+
Session = sessionmaker(bind=engine)
21+
22+
23+
COVID_CAST_META_URL = os.environ.get(
24+
"COVID_CAST_META_URL", "https://api.delphi.cmu.edu/epidata/covidcast/meta"
25+
)
26+
27+
28+
class Base(DeclarativeBase):
29+
pass
30+
31+
32+
class Signal(Base):
33+
34+
__tablename__ = "signals_signal"
35+
36+
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
37+
name = sqlalchemy.Column(sqlalchemy.String)
38+
display_name = sqlalchemy.Column(sqlalchemy.String)
39+
active = sqlalchemy.Column(sqlalchemy.Boolean)
40+
short_description = sqlalchemy.Column(sqlalchemy.String)
41+
description = sqlalchemy.Column(sqlalchemy.String)
42+
format_type = sqlalchemy.Column(sqlalchemy.String)
43+
time_type = sqlalchemy.Column(sqlalchemy.String)
44+
time_label = sqlalchemy.Column(sqlalchemy.String)
45+
is_smoothed = sqlalchemy.Column(sqlalchemy.Boolean)
46+
is_weighted = sqlalchemy.Column(sqlalchemy.Boolean)
47+
is_cumulative = sqlalchemy.Column(sqlalchemy.Boolean)
48+
has_stderr = sqlalchemy.Column(sqlalchemy.Boolean)
49+
has_sample_size = sqlalchemy.Column(sqlalchemy.Boolean)
50+
high_values_are = sqlalchemy.Column(sqlalchemy.String)
51+
base_id = sqlalchemy.Column(sqlalchemy.Integer)
52+
category_id = sqlalchemy.Column(sqlalchemy.Integer)
53+
source_id = sqlalchemy.Column(sqlalchemy.Integer)
54+
created = sqlalchemy.Column(sqlalchemy.DateTime)
55+
modified = sqlalchemy.Column(sqlalchemy.DateTime)
56+
last_updated = sqlalchemy.Column(sqlalchemy.Date)
57+
age_breakdown = sqlalchemy.Column(sqlalchemy.String)
58+
data_censoring = sqlalchemy.Column(sqlalchemy.Text)
59+
gender_breakdown = sqlalchemy.Column(sqlalchemy.Integer)
60+
missingness = sqlalchemy.Column(sqlalchemy.Text)
61+
race_breakdown = sqlalchemy.Column(sqlalchemy.Integer)
62+
reporting_cadence = sqlalchemy.Column(sqlalchemy.String)
63+
restrictions = sqlalchemy.Column(sqlalchemy.Text)
64+
severenity_pyramid_rungs = sqlalchemy.Column(sqlalchemy.String)
65+
temporal_scope_end = sqlalchemy.Column(sqlalchemy.String)
66+
temporal_scope_end_note = sqlalchemy.Column(sqlalchemy.Text)
67+
temporal_scope_start = sqlalchemy.Column(sqlalchemy.String)
68+
temporal_scope_start_note = sqlalchemy.Column(sqlalchemy.Text)
69+
typical_reporting_lag = sqlalchemy.Column(sqlalchemy.String)
70+
typical_revision_cadence = sqlalchemy.Column(sqlalchemy.String)
71+
license_id = sqlalchemy.Column(sqlalchemy.Integer)
72+
signal_type_id = sqlalchemy.Column(sqlalchemy.Integer)
73+
from_date = sqlalchemy.Column(sqlalchemy.Date)
74+
to_date = sqlalchemy.Column(sqlalchemy.Date)
75+
geographic_scope_id = sqlalchemy.Column(sqlalchemy.Integer)
76+
signal_availability_days = sqlalchemy.Column(sqlalchemy.Integer)
77+
78+
79+
class Source(Base):
80+
__tablename__ = "datasources_sourcesubdivision"
81+
82+
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
83+
name = sqlalchemy.Column(sqlalchemy.String)
84+
display_name = sqlalchemy.Column(sqlalchemy.String)
85+
description = sqlalchemy.Column(sqlalchemy.String)
86+
db_source = sqlalchemy.Column(sqlalchemy.String)
87+
data_source_id = sqlalchemy.Column(sqlalchemy.Integer)
88+
created = sqlalchemy.Column(sqlalchemy.DateTime)
89+
modified = sqlalchemy.Column(sqlalchemy.DateTime)
90+
external_name = sqlalchemy.Column(sqlalchemy.String)
91+
92+
93+
class SignalLastUpdatedParser:
94+
95+
def __init__(self, covidcast_meta_data: list) -> None:
96+
self.covidcast_meta_data = covidcast_meta_data
97+
self.year_week_date_format = "%Y-%W-%w"
98+
self.year_month_day_date_format = "%Y%m%d"
99+
100+
def format_date(
101+
self,
102+
date: str,
103+
) -> datetime:
104+
"""
105+
Format the date string to a specific format.
106+
107+
:param date: The date string to format.
108+
:return: The formatted date string.
109+
:rtype: str
110+
"""
111+
formated_date: datetime
112+
if len(date) == 6:
113+
year, week = date[:4], date[4:]
114+
logger.info(f"Date: {date}, year: {year}, week: {int(week)-1}")
115+
formated_date = datetime.strptime(
116+
f"{int(year)}-{int(week)-1}-1", self.year_week_date_format
117+
)
118+
elif len(date) == 8:
119+
formated_date = datetime.strptime(date, self.year_month_day_date_format)
120+
return formated_date
121+
122+
def set_data(self) -> None:
123+
"""
124+
Set the last updated date for signals in the database.
125+
"""
126+
with Session() as session:
127+
for db_source in self.covidcast_meta_data:
128+
for signal_data in db_source["signals"]:
129+
source = (
130+
session.query(Source)
131+
.filter(Source.name == signal_data["source"])
132+
.first()
133+
)
134+
last_updated = self.format_date(str(signal_data["max_issue"]))
135+
from_date = self.format_date(str(signal_data["min_time"]))
136+
to_date = self.format_date(str(signal_data["max_time"]))
137+
signal_availability_days = abs((to_date - from_date).days)
138+
try:
139+
session.execute(
140+
update(Signal)
141+
.where(Signal.name == signal_data["signal"])
142+
.where(Signal.source_id == source.id)
143+
.values(
144+
last_updated=last_updated,
145+
from_date=from_date,
146+
to_date=to_date,
147+
signal_availability_days=signal_availability_days,
148+
)
149+
)
150+
session.commit()
151+
logger.info(f"Signal {signal_data['signal']} successfully updated.")
152+
except AttributeError:
153+
logger.error(f"Failed to update signal {signal_data['signal']}. Probably the issue is with the source or source with name {signal_data['source']} does not exist.")
154+
155+
156+
def main():
157+
response = requests.get(COVID_CAST_META_URL)
158+
if response.status_code == 200:
159+
covidcast_meta_data = response.json()
160+
parser = SignalLastUpdatedParser(covidcast_meta_data)
161+
parser.set_data()
162+
else:
163+
logger.error(f"Failed to get data from {COVID_CAST_META_URL}")
164+
165+
166+
if __name__ == "__main__":
167+
main()

0 commit comments

Comments
 (0)