Skip to content

Commit

Permalink
Add command to report on funding opportunities
Browse files Browse the repository at this point in the history
  • Loading branch information
inglesp committed Jun 15, 2023
1 parent 05ba060 commit c63bad9
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 6 deletions.
3 changes: 2 additions & 1 deletion dotenv-sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# See comments in embbot/settings.py
EBMBOT_PATH=$PATH:/var/www/ebmbot/venv/bin/
LOGS_DIR=changeme
SLACK_LOGS_CHANNEL=changeme
SLACK_TECH_SUPPORT_CHANNEL=changeme
Expand All @@ -9,5 +10,5 @@ SLACK_APP_USERNAME=changeme
GITHUB_WEBHOOK_SECRET=changeme
EBMBOT_WEBHOOK_SECRET=changeme
WEBHOOK_ORIGIN=http(s)://changeme:1234
EBMBOT_PATH=$PATH:/var/www/ebmbot/venv/bin/
DATA_TEAM_GITHUB_API_TOKEN=changeme
GCP_CREDENTIALS_PATH=changeme/gcp-credentials.json
19 changes: 19 additions & 0 deletions ebmbot/job_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,25 @@
},
],
},
"funding": {
"python_file": "funding_report.py",
"jobs": {
"generate_report": {
"python_function": "main",
"run_args_template": "",
"report_stdout": True,
"report_format": "blocks",
}
},
"slack": [
{
"command": "report",
"help": "generate funding report",
"type": "schedule_job",
"job_type": "generate_report",
},
],
},
}
# fmt: on

Expand Down
6 changes: 4 additions & 2 deletions ebmbot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
env.read_env()

APPLICATION_ROOT = Path(__file__).resolve().parent.parent
EBMBOT_PATH = env.str("EBMBOT_PATH")

DB_PATH = env.path("DB_PATH", default=APPLICATION_ROOT / "ebmbot.db")
WORKSPACE_DIR = env.path("WORKSPACE_DIR", default=APPLICATION_ROOT / "workspace")
Expand All @@ -25,10 +26,11 @@
# "Secret" from https://github.com/ebmdatalab/openprescribing/settings/hooks/85994427
GITHUB_WEBHOOK_SECRET = env.str("GITHUB_WEBHOOK_SECRET").encode("ascii")

# Path to credentials of gdrive@ebmdatalab.iam.gserviceaccount.com GCP service account
GCP_CREDENTIALS_PATH = env.path("GCP_CREDENTIALS_PATH")

# A secret that we generate ourselves
EBMBOT_WEBHOOK_SECRET = env.str("EBMBOT_WEBHOOK_SECRET").encode("ascii")

# TTL in seconds for webhook token
EBMBOT_WEBHOOK_TOKEN_TTL = 60 * 60

EBMBOT_PATH = env.str("EBMBOT_PATH")
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ omit = [
"fabfile.py",
"*/__main__.py",
"tests/mock_web_api_server.py",
"workspace/funding_report.py",
"workspace/teamdata/generate_report.py",
"workspace/test/jobs.py"
]
Expand Down
3 changes: 3 additions & 0 deletions requirements.prod.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ structlog

# for op commands
django-dotenv

# for commands that read from Google Sheets
google-api-python-client
82 changes: 79 additions & 3 deletions requirements.prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ bcrypt==3.2.2 \
--hash=sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40 \
--hash=sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa
# via paramiko
cachetools==5.3.1 \
--hash=sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590 \
--hash=sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b
# via google-auth
certifi==2022.6.15 \
--hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d \
--hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412
Expand Down Expand Up @@ -138,10 +142,39 @@ flask==2.2.2 \
--hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \
--hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526
# via -r requirements.prod.in
google-api-core==2.11.0 \
--hash=sha256:4b9bb5d5a380a0befa0573b302651b8a9a89262c1730e37bf423cec511804c22 \
--hash=sha256:ce222e27b0de0d7bc63eb043b956996d6dccab14cc3b690aaea91c9cc99dc16e
# via google-api-python-client
google-api-python-client==2.89.0 \
--hash=sha256:0b0c9503df2da92692ffceee88423ca593cbf0b939d879e2c46fbdc1a39cf091 \
--hash=sha256:272ff339928ac35b1e117d30e5db444fb701803bb748bb29e7bb520be29dea36
# via -r requirements.prod.in
google-auth==2.20.0 \
--hash=sha256:030af34138909ccde0fbce611afc178f1d65d32fbff281f25738b1fe1c6f3eaa \
--hash=sha256:23b7b0950fcda519bfb6692bf0d5289d2ea49fc143717cc7188458ec620e63fa
# via
# google-api-core
# google-api-python-client
# google-auth-httplib2
google-auth-httplib2==0.1.0 \
--hash=sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10 \
--hash=sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac
# via google-api-python-client
googleapis-common-protos==1.59.1 \
--hash=sha256:0cbedb6fb68f1c07e18eb4c48256320777707e7d0c55063ae56c15db3224a61e \
--hash=sha256:b35d530fe825fb4227857bc47ad84c33c809ac96f312e13182bdeaa2abe1178a
# via google-api-core
gunicorn==20.1.0 \
--hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \
--hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
# via -r requirements.prod.in
httplib2==0.22.0 \
--hash=sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc \
--hash=sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81
# via
# google-api-python-client
# google-auth-httplib2
idna==3.3 \
--hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
--hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
Expand Down Expand Up @@ -214,6 +247,33 @@ paramiko==2.11.0 \
--hash=sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938 \
--hash=sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270
# via fabric3
protobuf==4.23.2 \
--hash=sha256:09310bce43353b46d73ba7e3bca78273b9bc50349509b9698e64d288c6372c2a \
--hash=sha256:20874e7ca4436f683b64ebdbee2129a5a2c301579a67d1a7dda2cdf62fb7f5f7 \
--hash=sha256:25e3370eda26469b58b602e29dff069cfaae8eaa0ef4550039cc5ef8dc004511 \
--hash=sha256:281342ea5eb631c86697e1e048cb7e73b8a4e85f3299a128c116f05f5c668f8f \
--hash=sha256:384dd44cb4c43f2ccddd3645389a23ae61aeb8cfa15ca3a0f60e7c3ea09b28b3 \
--hash=sha256:54a533b971288af3b9926e53850c7eb186886c0c84e61daa8444385a4720297f \
--hash=sha256:6c081863c379bb1741be8f8193e893511312b1d7329b4a75445d1ea9955be69e \
--hash=sha256:86df87016d290143c7ce3be3ad52d055714ebaebb57cc659c387e76cfacd81aa \
--hash=sha256:8da6070310d634c99c0db7df48f10da495cc283fd9e9234877f0cd182d43ab7f \
--hash=sha256:b2cfab63a230b39ae603834718db74ac11e52bccaaf19bf20f5cce1a84cf76df \
--hash=sha256:c52cfcbfba8eb791255edd675c1fe6056f723bf832fa67f0442218f8817c076e \
--hash=sha256:ce744938406de1e64b91410f473736e815f28c3b71201302612a68bf01517fea \
--hash=sha256:efabbbbac1ab519a514579ba9ec52f006c28ae19d97915951f69fa70da2c9e91
# via
# google-api-core
# googleapis-common-protos
pyasn1==0.5.0 \
--hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \
--hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde
# via
# pyasn1-modules
# rsa
pyasn1-modules==0.3.0 \
--hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \
--hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d
# via google-auth
pycparser==2.21 \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
Expand All @@ -233,20 +293,30 @@ pynacl==1.5.0 \
pyparsing==3.0.9 \
--hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \
--hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc
# via packaging
# via
# httplib2
# packaging
python-dotenv==0.20.0 \
--hash=sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f \
--hash=sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938
# via environs
requests==2.28.1 \
--hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
--hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
# via -r requirements.prod.in
# via
# -r requirements.prod.in
# google-api-core
rsa==4.9 \
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
# via google-auth
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# fabric3
# google-auth
# google-auth-httplib2
# paramiko
slack-bolt==1.15.2 \
--hash=sha256:18d60c99de0ff786a302dd872b2c0a63e46b6922b7ce2e352372effc321d0bdd \
Expand All @@ -260,10 +330,16 @@ structlog==22.1.0 \
--hash=sha256:760d37b8839bd4fe1747bed7b80f7f4de160078405f4b6a1db9270ccbfce6c30 \
--hash=sha256:94b29b1d62b2659db154f67a9379ec1770183933d6115d21f21aa25cfc9a7393
# via -r requirements.prod.in
uritemplate==4.1.1 \
--hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \
--hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e
# via google-api-python-client
urllib3==1.26.12 \
--hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \
--hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997
# via requests
# via
# google-auth
# requests
werkzeug==2.2.2 \
--hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \
--hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5
Expand Down
154 changes: 154 additions & 0 deletions workspace/funding_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import json
from datetime import date, datetime

from apiclient import discovery
from google.oauth2 import service_account

from ebmbot import settings


def main():
credentials = service_account.Credentials.from_service_account_file(
settings.GCP_CREDENTIALS_PATH,
scopes=["https://www.googleapis.com/auth/spreadsheets.readonly"],
)
service = discovery.build("sheets", "v4", credentials=credentials)

rows = (
service.spreadsheets()
.values()
.get(
spreadsheetId="18xM7nu1aD9dZe-eJbqrIRxinO5tjSBZv0EpJRlvz_BI",
range="Calls",
)
.execute()
)["values"]

headers = rows[0]
rows = [dict(zip(headers, row)) for row in rows[1:]]

calls_recently_added = []
calls_closing_soon = []

for row in rows:
opportunity = row["Opportunity"]
funder = row["Funder(s)"]
type_ = row["Type"]
link = row["Link (specific call)"] or row["Link (general funding stream)"]

award = row["Max award (£)"]
if not award.strip():
award = "£ Not stated"
elif award.isnumeric():
award = f"£{int(award):,}"

added_date = row["Added date"]
if not added_date:
continue
added_date = datetime.strptime(added_date, "%d %b %Y").date()
days_since_added = (date.today() - added_date).days

deadline_date = row["Deadline date"]
if not deadline_date:
continue
deadline_date = datetime.strptime(deadline_date, "%d %b %Y").date()
days_to_deadline = (deadline_date - date.today()).days

if days_since_added <= 14:
line = f"{type_}: <{link}|{opportunity}>, ({funder}, {award})"
calls_recently_added.append(
{
"type": type_,
"deadline_date": deadline_date,
"line": line,
}
)

if days_to_deadline <= 30:
line = f"{type_}: <{link}|{opportunity}>, ({funder}, {award}), closing {deadline_date} ({days_to_deadline} days)"
calls_closing_soon.append(
{
"type": type_,
"deadline_date": deadline_date,
"line": line,
}
)

types = ["Project", "Programme", "Fellowship", "Other"]

calls_recently_added.sort(
key=lambda row: (types.index(row["type"]), row["deadline_date"])
)
calls_closing_soon.sort(
key=lambda row: (types.index(row["type"]), row["deadline_date"])
)

blocks = [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":moneybag: *Funding update* :moneybag:",
},
},
]

if calls_recently_added:
blocks.extend(
[
{"type": "divider"},
{
"type": "section",
"text": {"type": "mrkdwn", "text": "*Recently added calls*"},
},
]
)

for call in calls_recently_added:
blocks.append(
{
"type": "section",
"text": {"type": "mrkdwn", "text": call["line"]},
}
)

if calls_closing_soon:
blocks.extend(
[
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Calls closing within 30 days*",
},
},
]
)

for call in calls_closing_soon:
blocks.append(
{
"type": "section",
"text": {"type": "mrkdwn", "text": call["line"]},
}
)

blocks.extend(
[
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Further details for all funding opportunities are available on the <https://docs.google.com/spreadsheets/d/18xM7nu1aD9dZe-eJbqrIRxinO5tjSBZv0EpJRlvz_BI/|funding tracker>.",
},
},
]
)

return json.dumps({"blocks": blocks}, indent=2)


if __name__ == "__main__":
print(main())

0 comments on commit c63bad9

Please sign in to comment.