Skip to content

Commit 0703763

Browse files
committed
Merge branch 'main' into claimshosp-hhs-nation
2 parents 451eeab + 7672030 commit 0703763

File tree

12 files changed

+448
-257
lines changed

12 files changed

+448
-257
lines changed

_delphi_utils_python/data_proc/geomap/geo_data_proc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ def derive_fips_hhs_crosswalk():
547547
)
548548
state_hhs = pd.read_csv(
549549
join(OUTPUT_DIR, STATE_HHS_OUT_FILENAME),
550-
dtype={"state_code": str, "state_id": str, "state_name": str},
550+
dtype={"state_code": str, "hhs": str},
551551
)
552552

553553
fips_pop["state_code"] = fips_pop["fips"].str[:2]
@@ -573,7 +573,7 @@ def derive_zip_hhs_crosswalk():
573573
)
574574
state_hhs = pd.read_csv(
575575
join(OUTPUT_DIR, STATE_HHS_OUT_FILENAME),
576-
dtype={"state_code": str, "state_id": str, "state_name": str},
576+
dtype={"state_code": str, "hhs": str},
577577
)
578578

579579
(

_delphi_utils_python/delphi_utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .export import create_export_csv
88
from .utils import read_params
99

10+
from .logger import get_structured_logger
1011
from .geomap import GeoMapper
1112
from .smooth import Smoother
1213
from .signal import add_prefix, public_signal

_delphi_utils_python/delphi_utils/data/zip_hhs_table.csv

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2780,7 +2780,6 @@ zip,weight,hhs
27802780
08901,1.0,2
27812781
08902,1.0,2
27822782
08904,1.0,2
2783-
>>>>>>> add-hhs
27842783
10001,1.0,2
27852784
10002,1.0,2
27862785
10003,1.0,2
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Structured logger utility for creating JSON logs in Delphi pipelines."""
2+
import logging
3+
import sys
4+
import structlog
5+
6+
def get_structured_logger(name=__name__):
7+
"""Create a new structlog logger.
8+
9+
Use the logger returned from this in indicator code using the standard
10+
wrapper calls, e.g.:
11+
12+
logger = get_structured_logger(__name__)
13+
logger.warning("Error", type="Signal too low").
14+
15+
The output will be rendered as JSON which can easily be consumed by logs
16+
processors.
17+
18+
See the structlog documentation for details.
19+
20+
Parameters
21+
---------
22+
name: Name to use for logger (included in log lines), __name__ from caller
23+
is a good choice.
24+
"""
25+
# Configure the underlying logging configuration
26+
logging.basicConfig(
27+
format="%(message)s",
28+
stream=sys.stdout,
29+
level=logging.INFO)
30+
31+
# Configure structlog. This uses many of the standard suggestions from
32+
# the structlog documentation.
33+
structlog.configure(
34+
processors=[
35+
# Filter out log levels we are not tracking.
36+
structlog.stdlib.filter_by_level,
37+
# Include logger name in output.
38+
structlog.stdlib.add_logger_name,
39+
# Include log level in output.
40+
structlog.stdlib.add_log_level,
41+
# Allow formatting into arguments e.g., logger.info("Hello, %s",
42+
# name)
43+
structlog.stdlib.PositionalArgumentsFormatter(),
44+
# Add timestamps.
45+
structlog.processors.TimeStamper(fmt="iso"),
46+
# Match support for exception logging in the standard logger.
47+
structlog.processors.StackInfoRenderer(),
48+
structlog.processors.format_exc_info,
49+
# Decode unicode characters
50+
structlog.processors.UnicodeDecoder(),
51+
# Render as JSON
52+
structlog.processors.JSONRenderer()
53+
],
54+
# Use a dict class for keeping track of data.
55+
context_class=dict,
56+
# Use a standard logger for the actual log call.
57+
logger_factory=structlog.stdlib.LoggerFactory(),
58+
# Use a standard wrapper class for utilities like log.warning()
59+
wrapper_class=structlog.stdlib.BoundLogger,
60+
# Cache the logger
61+
cache_logger_on_first_use=True,
62+
)
63+
64+
return structlog.get_logger(name)

_delphi_utils_python/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"pylint",
1313
"pytest",
1414
"pytest-cov",
15+
"structlog",
1516
"xlrd"
1617
]
1718

sir_complainsalot/delphi_sir_complainsalot/check_source.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from dataclasses import dataclass
22
from typing import List
3+
from delphi_utils import get_structured_logger
34

45
import covidcast
56
import numpy as np
67
import pandas as pd
78

9+
logger = get_structured_logger(__name__)
10+
811
@dataclass
912
class Complaint:
1013
message: str
@@ -83,6 +86,14 @@ def check_source(data_source, meta, params, grace):
8386
# No gap detection for this source
8487
continue
8588

89+
logger.info("Retrieving signal",
90+
source=data_source,
91+
signal=row["signal"],
92+
start_day=(row["max_time"] -
93+
gap_window).strftime("%Y-%m-%d"),
94+
end_day=row["max_time"].strftime("%Y-%m-%d"),
95+
geo_type=row["geo_type"])
96+
8697
latest_data = covidcast.signal(
8798
data_source, row["signal"],
8899
start_day=row["max_time"] - gap_window,

sir_complainsalot/delphi_sir_complainsalot/run.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
when the module is run with `python -m delphi_sir_complainsalot`.
66
"""
77

8+
import logging
9+
import structlog
810
import sys
911

1012
from itertools import groupby
@@ -13,10 +15,12 @@
1315
from slack.errors import SlackApiError
1416

1517
from delphi_utils import read_params
18+
from delphi_utils import get_structured_logger
1619
import covidcast
1720

1821
from .check_source import check_source
1922

23+
logger = get_structured_logger(__name__)
2024

2125
def run_module():
2226

@@ -30,7 +34,12 @@ def run_module():
3034

3135
if len(complaints) > 0:
3236
for complaint in complaints:
33-
print(complaint)
37+
logger.critical(event="signal out of SLA",
38+
message=complaint.message,
39+
data_source=complaint.data_source,
40+
signal=complaint.signal,
41+
geo_types=complaint.geo_types,
42+
last_updated=complaint.last_updated.strftime("%Y-%m-%d"))
3443

3544
report_complaints(complaints, params)
3645

@@ -46,14 +55,14 @@ def split_complaints(complaints, n=49):
4655
def report_complaints(all_complaints, params):
4756
"""Post complaints to Slack."""
4857
if not params["slack_token"]:
49-
print("\b (dry-run)")
58+
logger.info("(dry-run)")
5059
return
5160

5261
client = WebClient(token=params["slack_token"])
5362

5463
for complaints in split_complaints(all_complaints):
5564
blocks = format_complaints_aggregated_by_source(complaints)
56-
print(f"blocks: {len(blocks)}")
65+
logger.info(f"blocks: {len(blocks)}")
5766
try:
5867
client.chat_postMessage(
5968
channel=params["channel"],
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""Validation output reports."""
2+
import sys
3+
from datetime import date, datetime
4+
from typing import List, Tuple
5+
6+
class ValidationReport:
7+
"""Class for reporting the results of validation."""
8+
def __init__(self, errors_to_suppress: List[Tuple[str]]):
9+
"""Initialize a ValidationReport.
10+
Parameters
11+
----------
12+
errors_to_suppress: List[Tuple[str]]
13+
List of error identifications to ignore.
14+
15+
Attributes
16+
----------
17+
errors_to_suppress: List[Tuple[str]]
18+
See above
19+
num_suppressed: int
20+
Number of errors suppressed
21+
total_checks: int
22+
Number of validation checks performed
23+
raised_errors: List[Exception]
24+
Errors raised from validation failures
25+
raised_warnings: List[Exception]
26+
Warnings raised from validation execution
27+
unsuppressed_errors: List[Exception]
28+
Errors raised from validation failures not found in `self.errors_to_suppress`
29+
"""
30+
self.errors_to_suppress = errors_to_suppress.copy()
31+
self.num_suppressed = 0
32+
self.total_checks = 0
33+
self.raised_errors = []
34+
self.raised_warnings = []
35+
self.unsuppressed_errors = []
36+
37+
def add_raised_error(self, error):
38+
"""Add an error to the report.
39+
Parameters
40+
----------
41+
error: Exception
42+
Error raised in validation
43+
44+
Returns
45+
-------
46+
None
47+
"""
48+
self.raised_errors.append(error)
49+
# Convert any dates in check_data_id to strings for the purpose of comparing
50+
# to manually suppressed errors.
51+
raised_check_id = tuple([
52+
item.strftime("%Y-%m-%d") if isinstance(item, (date, datetime))
53+
else item for item in error.check_data_id])
54+
55+
if raised_check_id in self.errors_to_suppress:
56+
self.errors_to_suppress.remove(raised_check_id)
57+
self.num_suppressed += 1
58+
else:
59+
self.unsuppressed_errors.append(error)
60+
61+
def increment_total_checks(self):
62+
"""Records a check."""
63+
self.total_checks += 1
64+
65+
def add_raised_warning(self, warning):
66+
"""Add a warning to the report.
67+
Parameters
68+
----------
69+
warning: Warning
70+
Warning raised in validation
71+
72+
Returns
73+
-------
74+
None
75+
"""
76+
self.raised_warnings.append(warning)
77+
78+
def __str__(self):
79+
"""String representation of report."""
80+
out_str = f"{self.total_checks} checks run\n"
81+
out_str += f"{len(self.unsuppressed_errors)} checks failed\n"
82+
out_str += f"{self.num_suppressed} checks suppressed\n"
83+
out_str += f"{len(self.raised_warnings)} warnings\n"
84+
for message in self.unsuppressed_errors:
85+
out_str += f"{message}\n"
86+
for message in self.raised_warnings:
87+
out_str += f"{message}\n"
88+
return out_str
89+
90+
def print_and_exit(self):
91+
"""
92+
Print results and, if any not-suppressed exceptions were raised, exit with non-zero status.
93+
"""
94+
print(self)
95+
if len(self.unsuppressed_errors) != 0:
96+
sys.exit(1)
97+
else:
98+
sys.exit(0)

validator/delphi_validator/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ def run_module():
1414
params = parent_params['validation']
1515

1616
validator = Validator(params)
17-
validator.validate(parent_params["export_dir"])
17+
validator.validate(parent_params["export_dir"]).print_and_exit()

0 commit comments

Comments
 (0)