reupload commit

This commit is contained in:
Nico 2022-02-19 20:16:53 +01:00
commit f98e66f9fe
92 changed files with 9874 additions and 0 deletions

48
.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
db.sqlite3
.env
# vs code
.vscode
.tx
############################
# Node
############################
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Dependency directories
node_modules
# Optional npm cache directory
.npm
# Yarn Integrity file
.yarn-integrity
############################
# OS X
############################
.DS_Store
.AppleDouble
.LSOverride
Icon
.Spotlight-V100
.Trashes
._*
############################
# Windows
############################
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msm
*.msp

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Bastian Greshake Tzovaras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
Pipfile Normal file
View File

@ -0,0 +1,16 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
django = "*"
django-open-humans = "*"
django-heroku = "*"
gunicorn = "*"
ipython = "*"
[requires]
python_version = "3.6"

274
Pipfile.lock generated Normal file
View File

@ -0,0 +1,274 @@
{
"_meta": {
"hash": {
"sha256": "73f95eab65c4ba41b6746ad892f9cd561ffd9d6c952979dc6daf342b697c1f4d"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"arrow": {
"hashes": [
"sha256:a24c1de90850f6fb2033fd6bf8a11f281e84cb54825e5eabdda219e673b52aac",
"sha256:eb5d339f00072cc297d7de252a2e75f272085d1231a3723f1026d1fa91367118"
],
"version": "==0.15.6"
},
"asgiref": {
"hashes": [
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
],
"version": "==3.2.7"
},
"backcall": {
"hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
"sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
],
"version": "==0.1.0"
},
"certifi": {
"hashes": [
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
],
"version": "==2020.4.5.1"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"version": "==7.1.2"
},
"decorator": {
"hashes": [
"sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
"sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
],
"version": "==4.4.2"
},
"dj-database-url": {
"hashes": [
"sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163",
"sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"
],
"version": "==0.5.0"
},
"django": {
"hashes": [
"sha256:5052b34b34b3425233c682e0e11d658fd6efd587d11335a0203d827224ada8f2",
"sha256:e1630333248c9b3d4e38f02093a26f1e07b271ca896d73097457996e0fae12e8"
],
"index": "pypi",
"version": "==3.0.7"
},
"django-heroku": {
"hashes": [
"sha256:2bc690aab89eedbe01311752320a9a12e7548e3b0ed102681acc5736a41a4762",
"sha256:6af4bc3ae4a9b55eaad6dbe5164918982d2762661aebc9f83d9fa49f6009514e"
],
"index": "pypi",
"version": "==0.3.1"
},
"django-open-humans": {
"hashes": [
"sha256:8b0b0831c3729001b156f06fc801a20b5d6b8d6abf6f5f5e019d123ecc53ec3d"
],
"index": "pypi",
"version": "==0.1.5"
},
"gunicorn": {
"hashes": [
"sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626",
"sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"
],
"index": "pypi",
"version": "==20.0.4"
},
"humanfriendly": {
"hashes": [
"sha256:bf52ec91244819c780341a3438d5d7b09f431d3f113a475147ac9b7b167a3d12",
"sha256:e78960b31198511f45fd455534ae7645a6207d33e512d2e842c766d15d9c8080"
],
"version": "==8.2"
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
"version": "==2.9"
},
"ipython": {
"hashes": [
"sha256:5b241b84bbf0eb085d43ae9d46adf38a13b45929ca7774a740990c2c242534bb",
"sha256:f0126781d0f959da852fb3089e170ed807388e986a8dd4e6ac44855845b0fb1c"
],
"index": "pypi",
"version": "==7.14.0"
},
"ipython-genutils": {
"hashes": [
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
],
"version": "==0.2.0"
},
"jedi": {
"hashes": [
"sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798",
"sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030"
],
"version": "==0.17.0"
},
"open-humans-api": {
"hashes": [
"sha256:c3328e5ee6a596b9ff653525e65252bb499ccfcb9ed330c0f86742d4d7dd228e"
],
"version": "==0.2.9"
},
"parso": {
"hashes": [
"sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0",
"sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"
],
"version": "==0.7.0"
},
"pexpect": {
"hashes": [
"sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
],
"markers": "sys_platform != 'win32'",
"version": "==4.8.0"
},
"pickleshare": {
"hashes": [
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
"sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
],
"version": "==0.7.5"
},
"prompt-toolkit": {
"hashes": [
"sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8",
"sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"
],
"version": "==3.0.5"
},
"psycopg2": {
"hashes": [
"sha256:132efc7ee46a763e68a815f4d26223d9c679953cd190f1f218187cb60decf535",
"sha256:2327bf42c1744a434ed8ed0bbaa9168cac7ee5a22a9001f6fc85c33b8a4a14b7",
"sha256:27c633f2d5db0fc27b51f1b08f410715b59fa3802987aec91aeb8f562724e95c",
"sha256:2c0afb40cfb4d53487ee2ebe128649028c9a78d2476d14a67781e45dc287f080",
"sha256:2df2bf1b87305bd95eb3ac666ee1f00a9c83d10927b8144e8e39644218f4cf81",
"sha256:440a3ea2c955e89321a138eb7582aa1d22fe286c7d65e26a2c5411af0a88ae72",
"sha256:6a471d4d2a6f14c97a882e8d3124869bc623f3df6177eefe02994ea41fd45b52",
"sha256:6b306dae53ec7f4f67a10942cf8ac85de930ea90e9903e2df4001f69b7833f7e",
"sha256:a0984ff49e176062fcdc8a5a2a670c9bb1704a2f69548bce8f8a7bad41c661bf",
"sha256:ac5b23d0199c012ad91ed1bbb971b7666da651c6371529b1be8cbe2a7bf3c3a9",
"sha256:acf56d564e443e3dea152efe972b1434058244298a94348fc518d6dd6a9fb0bb",
"sha256:d3b29d717d39d3580efd760a9a46a7418408acebbb784717c90d708c9ed5f055",
"sha256:f7d46240f7a1ae1dd95aab38bd74f7428d46531f69219954266d669da60c0818"
],
"version": "==2.8.5"
},
"ptyprocess": {
"hashes": [
"sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
"sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
],
"version": "==0.6.0"
},
"pygments": {
"hashes": [
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
],
"version": "==2.6.1"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"version": "==2.8.1"
},
"pytz": {
"hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
"version": "==2020.1"
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
"version": "==2.23.0"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.15.0"
},
"sqlparse": {
"hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.1"
},
"traitlets": {
"hashes": [
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
"sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"
],
"version": "==4.3.3"
},
"urllib3": {
"hashes": [
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
],
"version": "==1.25.9"
},
"wcwidth": {
"hashes": [
"sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6",
"sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"
],
"version": "==0.2.3"
},
"whitenoise": {
"hashes": [
"sha256:60154b976a13901414a25b0273a841145f77eb34a141f9ae032a0ace3e4d5b27",
"sha256:6dd26bfda3af29177d8ab7333a0c7b7642eb615ce83764f4d15a9aecda3201c4"
],
"version": "==5.1.0"
}
},
"develop": {}
}

2
Procfile Normal file
View File

@ -0,0 +1,2 @@
release: python manage.py migrate
web: gunicorn open_survey.wsgi:application --log-file -

80
README.md Normal file
View File

@ -0,0 +1,80 @@
## Demo of how Open Humans to OpenClinica workflow can work
### Install
Assumes you have a basic understanding of how Django, `pipenv`, & `heroku` work:
- `pipenv install`
- `pipenv shell`
- `heroku local:run python manage.py migrate`
- `heroku local`
Web-server should be up on `127.0.0.1:5000`
### Setup
Copy the `env.sample` as `.env` and fill in the missing bits. Required:
- An Open Humans project
- Having a _Participate_ enabled Open Clinica survey and the corresponding token to add participants etc.
Demo deployment runs on https://opensurveytest.herokuapp.com/
Logging in with Open Humans will:
- Create Open Clinica participant
- Schedule first survey for them
- Get participant access survey_token
- Email this via Open Humans to just registered participant
### Design dev
Assumes you have a basic understanding of how node and yarn/npm work.
Node version used: 12.16.1
We use [PostCSS](https://github.com/postcss/postcss) to enhance browser compatibility, accessibility and preformances.
A `package.json` and a `postcss.config.js` config file exist at the document root. Add or remove plugin from there.
In order to edit the styles, you need to:
- run `yarn install` or `npm i`
- edit the styles in `src > css > parts`
- run `yarn watch` or `npm run watch` while in developement (watches the changes)
- run `yarn build` or `npm run dev` for production
The optimized and minified style file is under `static > css`
### Translations / i18n
#### Activate a new language
To activate a new language edit the **LANGUAGES** setting in **settings.py**
```
LANGUAGES = [
('en', _('English')),
('fr', _('French')),
('de', _('German')),
]
```
You need to do this first before translating because the language name itself needs to be translated.
#### Add a new language
To add a new language simply add a new directory in /locale with the language code as name, for example:
**/locale/fr** for french.
Then to generate the translation files run:
`python manage.py makemessages`
It will create **django.po** files inside each /\<lang\>/LC_MESSAGES directory.
You can edit these files directly with any text editor or specialized software / web application.
Once the translations are complete, to generate the binary files optimized for consumption by Django, run:
`python manage.py compilemessages`
It will create **django.mo** files inside each /\<lang\>/LC_MESSAGES directory.

BIN
_design/index.sketch Normal file

Binary file not shown.

BIN
dump.rdb Normal file

Binary file not shown.

26
env.sample Normal file
View File

@ -0,0 +1,26 @@
SECRET_KEY="SOME_KEY"
DEBUG="True"
# Setup for Open Humans backend
OPENHUMANS_APP_BASE_URL="http://127.0.0.1:5000/"
OPENHUMANS_CLIENT_ID="CLIENT_ID"
OPENHUMANS_CLIENT_SECRET="CLIENT_SECRET"
ON_HEROKU='True'
# Setup for Open Clinica
## Username for adding participants & scheduling data ('site investigator' role)
OPENCLINICA_USERNAME="Username"
OPENCLINICA_PASSWORD="Password"
## Username for exporting data ('data manager' role)
OPENCLINICA_DATA_USERNAME="AnotherUsername"
OPENCLINICA_DATA_PASSWORD="Password"
OPENCLINICA_STUDY="DEMO_NAME(TEST)"
OPENCLINICA_SITE="SITE_NAME(TEST)"
OPENCLINICA_PARTICIPATE_LINK="PARTICIPATE_LINK.mytrial.me"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

1
main/__init__.py Normal file
View File

@ -0,0 +1 @@
default_app_config = 'main.apps.MainConfig'

3
main/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

8
main/apps.py Normal file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
class MainConfig(AppConfig):
name = 'main'
def ready(self):
import main.signals

124
main/helpers.py Normal file
View File

@ -0,0 +1,124 @@
import datetime
import requests
import json
from urllib.parse import urljoin
from .models import ReportToken
from django.utils import translation
from django.conf import settings
from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _
def get_access_token(download=False):
url = 'https://opencovid.build.openclinica.io/user-service/api/oauth/token'
headers = {'Content-Type': "application/json"}
data = json.dumps(
{'username': settings.OPENCLINICA_SITE_USERNAME, 'password': settings.OPENCLINICA_SITE_PASSWORD}
)
if download:
data = json.dumps(
{'username': settings.OPENCLINICA_DATA_USERNAME, 'password': settings.OPENCLINICA_DATA_PASSWORD}
)
response = requests.post(url, headers=headers, data=data)
if response.status_code == 200:
return response.text
else:
raise ValueError("{} returned: {}".format(url, response.text))
def create_openclinica_user(survey_member):
headers = {
"Authorization": "bearer {}".format(get_access_token()),
"Content-Type": "application/json"
}
data = {
"identifier": survey_member.member.oh_id,
"subjectKey": survey_member.member.oh_id
}
url = "https://opencovid.openclinica.io/OpenClinica/pages/auth/api/clinicaldata/studies/{}/sites/{}/participants?register=y".format(
settings.OPENCLINICA_STUDY, settings.OPENCLINICA_SITE
)
response = requests.post(url, headers=headers, data=json.dumps(data))
if response.status_code == 200:
return response.text
else:
raise ValueError("{} returned: {}".format(url, response.text))
def create_openclinica_event(survey_member, event, date):
headers = {
"Authorization": "bearer {}".format(get_access_token()),
"Content-Type": "application/json"
}
data = {
"endDate": date,
"startDate": date,
"studyEventOID": event,
"subjectKey": survey_member.member.oh_id,
}
url = "https://opencovid.openclinica.io/OpenClinica/pages/auth/api/clinicaldata/studies/{}/sites/{}/events".format(
settings.OPENCLINICA_STUDY, settings.OPENCLINICA_SITE
)
response = requests.post(url, headers=headers, data=json.dumps(data))
if response.status_code == 200:
return response.text
else:
try:
# Does onboarding event already exists on OpenClinica ?
if response.json().get("message") == "errorCode.eventAlreadyExists":
return response.text
except:
raise ValueError("{} returned: {}".format(url, response.text))
def get_openclinica_token(survey_member):
headers = {
"Authorization": "bearer {}".format(get_access_token()),
"Content-Type": "application/json"
}
url = "https://opencovid.openclinica.io/OpenClinica/pages/auth/api/clinicaldata/studies/{}/sites/{}/participant?includeParticipateInfo=y&participantID={}".format(
settings.OPENCLINICA_STUDY, settings.OPENCLINICA_SITE, survey_member.member.oh_id
)
response = requests.get(url, headers=headers)
print(response.json())
if response.status_code == 200:
survey_member.survey_token = response.json()['accessCode']
survey_member.save()
return response.text
else:
raise ValueError("{} returned: {}".format(url, response.text))
def create_autologin_url(member, token):
url = urljoin(
settings.OPENHUMANS_APP_BASE_URL,
reverse("autologin", kwargs={"oh_id":member.oh_id}) + "?login_token={}".format(token.token)
)
return url
def create_survey_url(member, token):
url = create_autologin_url(member, token) + "&next=/survey"
return url
def send_user_survey_link(survey_member):
token = ReportToken(member=survey_member.member)
token.save()
url = create_survey_url(survey_member.member, token)
withdraw_url = create_autologin_url(survey_member.member, token)
saved_language = survey_member.member.surveyaccount.language
translation.activate(saved_language)
# make str out of subject as otherwise only last character is subject!
survey_member.member.message(
subject=str(_("Here's your survey link!")),
message="{}: {}\n\n\n{}: {}".format(
_("Please use this link to fill out the survey"),
url,
_("If you don't want to take part in the survey anymore, please use this link and click on \"WITHDRAW MY CONSENT\""),
withdraw_url
)
)
survey_member.last_email = datetime.date.today()
survey_member.save()

View File

View File

@ -0,0 +1,21 @@
from django.core.management.base import BaseCommand
import requests
from main.helpers import get_access_token
class Command(BaseCommand):
help = "Exports data for all members"
def handle(self, *args, **options):
headers = {
"Authorization": "bearer {}".format(get_access_token(download=True)),
"Accept": "application/json"
}
url = "https://opencovid.openclinica.io/OpenClinica/pages/auth/api/clinicaldata/S_DEMO_BGT(TEST)/91048557/*/*?includeAudits=n&includeDNs=n&includeMetadata=y&showArchived=n"
# download all data:
# url = "https://opencovid.openclinica.io/OpenClinica/pages/auth/api/clinicaldata/S_DEMO_BGT(TEST)/*/*/*?includeAudits=n&includeDNs=n&includeMetadata=y&showArchived=n"
response = requests.get(url, headers=headers)
print(response.json())
# TODO: so far it doesn't process the data, next step:
# - Save data to Open Humans for each member

View File

@ -0,0 +1,16 @@
from django.core.management.base import BaseCommand
from main.models import SurveyAccount
from main.helpers import get_openclinica_token
from main.helpers import send_user_survey_link
class Command(BaseCommand):
help = "Updates all data for all members"
def handle(self, *args, **options):
for survey_account in SurveyAccount.objects.filter(consent_given=True, daily_emails_activated=True):
# we no longer create events on sending out email, it's dynamically
# done when clicking the email link now
# create_openclinica_event(survey_account, "SE_DAILY", str(datetime.date.today()))
get_openclinica_token(survey_account)
send_user_survey_link(survey_account)

41
main/middleware.py Normal file
View File

@ -0,0 +1,41 @@
from django.conf import settings
from django.utils import translation
from openhumans.models import OpenHumansMember
import logging
logger = logging.getLogger(__name__)
class OpenHumansLoginUrlMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.openhumans_login_url = OpenHumansMember.get_auth_url()
response = self.get_response(request)
return response
class LoginLangMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if request.user.is_authenticated:
current_language = translation.get_language()
saved_language = request.user.openhumansmember.surveyaccount.language
if saved_language != current_language and saved_language in [lang[0] for lang in settings.LANGUAGES]:
response.set_cookie(
settings.LANGUAGE_COOKIE_NAME, saved_language,
max_age=settings.LANGUAGE_COOKIE_AGE,
path=settings.LANGUAGE_COOKIE_PATH,
domain=settings.LANGUAGE_COOKIE_DOMAIN,
secure=settings.LANGUAGE_COOKIE_SECURE,
httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
samesite=settings.LANGUAGE_COOKIE_SAMESITE,
)
return response

View File

@ -0,0 +1,24 @@
# Generated by Django 3.0.6 on 2020-05-18 10:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('openhumans', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SurveyAccount',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('survey_token', models.BooleanField(blank=True)),
('member', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='openhumans.OpenHumansMember')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-05-18 11:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='surveyaccount',
name='survey_token',
field=models.TextField(blank=True, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-05-26 12:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0002_auto_20200518_1123'),
]
operations = [
migrations.AddField(
model_name='surveyaccount',
name='last_survey',
field=models.DateField(blank=True, null=True),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.0.6 on 2020-05-26 13:14
from django.db import migrations, models
import django.db.models.deletion
import main.models
class Migration(migrations.Migration):
dependencies = [
('openhumans', '0001_initial'),
('main', '0003_surveyaccount_last_survey'),
]
operations = [
migrations.CreateModel(
name='ReportToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('token', models.TextField(default=main.models.create_token)),
('minutes_valid', models.IntegerField(default=1440)),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='openhumans.OpenHumansMember')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-06-08 11:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0004_reporttoken'),
]
operations = [
migrations.AddField(
model_name='surveyaccount',
name='consent_given',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-06-23 09:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0005_surveyaccount_consent_given'),
]
operations = [
migrations.AddField(
model_name='surveyaccount',
name='language',
field=models.CharField(default='en', max_length=5),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-06-25 12:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0006_surveyaccount_language'),
]
operations = [
migrations.AddField(
model_name='surveyaccount',
name='last_email',
field=models.DateField(blank=True, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-06-30 09:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0007_surveyaccount_last_email'),
]
operations = [
migrations.AddField(
model_name='surveyaccount',
name='daily_emails_activated',
field=models.BooleanField(default=True),
),
]

View File

45
main/models.py Normal file
View File

@ -0,0 +1,45 @@
from django.db import models
from openhumans.models import OpenHumansMember
import secrets
import datetime
from django.utils.timezone import now
class SurveyAccount(models.Model):
"""
Store additional data for an Open humans member.
This is a one to one relationship with a OpenHumansMember object.
"""
member = models.OneToOneField(OpenHumansMember, on_delete=models.CASCADE)
survey_token = models.TextField(blank=True, null=True)
last_survey = models.DateField(blank=True, null=True)
last_email = models.DateField(blank=True, null=True)
consent_given = models.BooleanField(default=False)
daily_emails_activated = models.BooleanField(default=True)
language = models.CharField(max_length=5, default='en')
TOKEN_EXPIRATION_MINUTES = 1440 # default expiration is one day
def create_token():
return secrets.token_urlsafe(16)
class ReportToken(models.Model):
member = models.ForeignKey(OpenHumansMember, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
token = models.TextField(default=create_token)
minutes_valid = models.IntegerField(default=TOKEN_EXPIRATION_MINUTES)
def is_valid(self):
expires = self.created + datetime.timedelta(minutes=self.minutes_valid)
if expires > now():
return True
return False
def valid_member(self):
if self.is_valid():
return self.member
return None

18
main/signals.py Normal file
View File

@ -0,0 +1,18 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from openhumans.models import OpenHumansMember
from .models import SurveyAccount
from .helpers import create_openclinica_user, create_openclinica_event
import datetime
@receiver(post_save, sender=OpenHumansMember)
def my_handler(sender, instance, created, **kwargs):
if created:
survey_account = SurveyAccount.objects.create(
member=instance
)
survey_account.save()
create_openclinica_user(survey_account)
create_openclinica_event(survey_account, "SE_FSFD", str(datetime.date.today()))

324
main/templates/base.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,106 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block intro %}
<section class="intro">
<div class="intro-content">
<h1>{% trans "About" %}</h1>
<p>
{% blocktrans %}
The Covid Open survey was created by a team of scientists at the Center for Research and Interdisciplinarity (CRI)
in
Paris, with collaborating researchers from the USA, Australia, Canada, Costa Rica, India and more! We are
interested
in bringing together minds from all over the world, yours included, to come up with great ideas to research the COVID-19 virus.
{% endblocktrans %}
</p>
<p>
<a href="/team">{% blocktrans %}Meet the team!{% endblocktrans %}</a>
</p>
</div>
</section>
{% endblock intro %}
{% block main %}
<section>
<article>
<h2>{% trans "Citizen Science" %}</h2>
<h3>{% trans "Be part of the team." %}</h3>
<p>
{% blocktrans %}
When it comes to science, we believe in harnessing the ingenuity of all citizens. We want you to contribute your
own survey items according to your research questions and interests. This will not only help us develop the study
to cover the needs and interests of the cohort, but will also help us understand key questions and concerns that
citizens outside of academia have regarding the pandemic.
{% endblocktrans %}
</p>
<h3>{% trans "How it works." %}</h3>
<p>
{% blocktrans %}
You will need to access the Open Humans forum, where we have created a dedicated section for your suggestions. Your
participation in this forum cannot be linked to your answers in the Covid Open survey. You can also create and engage in topics of
discussion directly on the forum and also vote for suggestions others have made!
{% endblocktrans %}
</p>
<h3>{% trans "What to tell us:" %}</h3>
<p>
{% trans "We want to add questions to the survey with an end-goal in mind. We therefore ask you to suggest:" %}
<ul>
<li>{% trans "A research hypothesis that you would like to test" %}</li>
<li>{% trans "A quick description of why you think your idea is worth exploring" %}</li>
<li>{% trans "The questions you would like to incorporate in the survey to test your hypothesis" %}</li>
</ul>
</p>
<h3>{% trans "How we review it." %}</h3>
<p>
{% blocktrans %}
Every fortnight, our team will go through your suggestions, and decide on a few to incorporate into our
next iteration of the survey. We will then send the new set of questions to be reviewed by the Institutional Review
Board, to make sure it doesnt go against any legal or ethical regulations. Once we get the green light, we will code the new questions into the survey.
{% endblocktrans %}
</p>
</article>
<article>
<h2>{% trans "Our approach" %}</h2>
<h3>{% trans "What we do." %}</h3>
<p>
{% blocktrans %}
The Covid Open survey questionnaire has been created for people who want to help fight the COVID-19 virus from
the comfort of their home. It consists of a short questionnaire that takes about 20 minutes to complete the first time, followed by a much quicker daily check-in on how you are feeling. A
wide variety of questions are asked, some of which can be answered daily, others that appear on a rolling basis, in order to keep the survey fresh!
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
Our goal? To better understand the virus transmission patterns and its effect on physical and mental health. We want
to understand <strong>how the virus spreads</strong>, <strong>who it affects most</strong>, and <strong>how it impacts our daily lives</strong>. We hope this may
guide decisions surrounding public health and medication repurposing in the future.
{% endblocktrans %}
</p>
<h3>{% trans "How we do it." %}</h3>
<p>
{% blocktrans %}
All data collected is anonymized and goes through the Open Humans platform. We also seek to promote citizen science
and provide an opportunity for you to contribute to this research
hypotheses that could be implemented in our fast-evolving survey.
{% endblocktrans %}
</p>
<p>
{% trans "Our inital research aims are as follows:" %}
<ul>
<li>
{% trans "To collect comprehensive symptom reports with a high temporal resolution to better understand COVID-19 disease progression." %}
</li>
<li>{% trans "To measure the impact of the COVID-19 pandemic on mental health." %}</li>
<li>{% trans "To identify effective agents to reduce COVID-19 disease severity" %}</li>
<li>{% trans "To characterize factors linked to increased susceptibility to COVID-19" %}</li>
<li>
{% trans "To understand how a more participatory approach to survey research - that makes participants esearch partners - changes survey success." %}
</li>
</ul>
<p>
</article>
</section>
{% endblock main %}

View File

@ -0,0 +1,40 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block main %}
<h1 class="mt-3 text-center">{% trans "Be a Citizen Scientist" %}</h1>
<p>
{% blocktrans %}
When it comes to science, we believe in harnessing the ingenuity of all citizens. We want you to contribute your
own survey items according to your research questions and interests. This will not only help us develop the
study to cover the needs and interests of the cohort, but will also help us understand key questions and
concerns that citizens outside of academia have regarding the pandemic.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
How it works: You will need to access the Open Humans forum, where a dedicated section for suggestions has been
created. Your personal information will be anonymized for your suggestions. You can also create and engage in
topics of discussions directly on the forum, as well as vote for suggestions others have made!
{% endblocktrans %}
</p>
<p>{% trans "What to tell us: We want to add questions to the survey with an end-goal in mind. We therefore ask you to suggest:" %}</p>
<ul>
<li>{% trans "a research hypothesis that you would like to test" %}</li>
<li>{% trans "A quick description of why you think your idea is worth exploring" %}</li>
<li>{% trans "The questions you would like to incorporate in the survey to test your hypothesis" %}</li>
</ul>
<p>
{% blocktrans %}
How we review it: Every fortnight, our team will go through your suggestions, and decide on a few that we could
incorporate into our next survey iteration . We will then send the new set of questions to be reviewed by the
Institutional Review Board, to make sure it doesnt go against any legal or ethical regulations. Once we get the
green light, we will code it into the survey.
{% endblocktrans %}
</p>
{% endblock main %}

View File

@ -0,0 +1,31 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block intro %}
<section class="intro">
<div class="intro-content">
<h1>{% trans "Access the Data" %}</h1>
<p>
{% blocktrans %}
Your data belongs to you! If you want to get a transcript with your past responses to the survey, <a
href="#">click here!</a> You will be redirected to our Open Humans server which stores your data securely.
{% endblocktrans %}
</p>
</div>
</section>
{% endblock intro %}
{% block main %}
<section>
<article>
<p>
{% blocktrans %}
As the survey just launched, we are waiting a few days to gain enough data points to show you the trends! Come
back later and see whats happening around you!
{% endblocktrans %}
</p>
</article>
</section>
{% endblock main %}

View File

@ -0,0 +1,219 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block intro %}
<section class="intro">
<div class="intro-content">
<h1>{% trans "Frequently Asked Questions" %}</h1>
<p>
{% blocktrans %}
Find answers to the most frequently asked questions about the Covid Open survey and the data we collect.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
Feel free to email us with any other question you may have at: <a href="mailto:bastian.greshake-tzovaras@cri-paris.org">bastian.greshake-tzovaras@cri-paris.org</a>
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
Thank you!
{% endblocktrans %}
</p>
</div>
</section>
{% endblock intro %}
{% block main %}
<section>
<article>
<h2>{% trans "The data" %}</h2>
<h3 data-collapse aria-expanded="false">{% trans "Who will be using my data?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
De-identified data will be made available to the scientific community, and will be distributed with any
publications
that arise from this study. If necessary to protect the privacy of participants, individual bits of data might be
removed or only shared in aggregate. These data cannot be used to identify participants. Additionally, each
participant will be given a copy of their own data as part of this study.
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "How will my data be protected?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
All data will be stored on a firewall and password-protected on secure project servers of Open Humans &
OpenClinica.
This study does not collect individual identifiers such as name or email addresses. Instead, Open Humans assigns
each participant a study ID on enrollment.
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "Will someone make money off my data?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
Participant data will not be sold and data sharing will be performed within strict protocol-driven procedural
guidelines.
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "How long will my data be used for?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
The collection period is aimed to end in May 2022. After this we will stop gathering new data but will need some
time to analyze everything collected.
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "Can I delete my data?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<div class="collapse__item">
<p>
{% blocktrans %}
Yes! Your data belongs to you. You can do so automatically from here, or manually through the Open Humans website.
{% endblocktrans %}
</p>
{% if not request.user.is_authenticated %}
<p><i>{% trans "Please login to view the automatic delete links" %}.</i></p>
{% else %}
<p class="collapse__item">{% trans "Follow these steps to delete your data automatically:" %}
<ul>
<li>{% blocktrans count counter=files|length %}You have 1 data file on Open Humans{% plural %}You have
{{ counter }} data files on Open Humans{% endblocktrans %}. {% if files|length != 0 %}<a
href="{% url "delete_all_openhuman_files" %}">{% trans "Click here to delete all your data files" %}</a>.{% else %}{% trans "You don't have anything to delete!" %}{% endif %}
</li>
<li>
{% trans "Additionally, if you want to revoke access from Covid Open Survey to your Open Humans account, follow theses steps:" %}
<ul>
<li>{% blocktrans %}Go to <a href="https://www.openhumans.org/activity/covid-open-survey/"
target="_blank">https://www.openhumans.org/activity/covid-open-survey/</a>{% endblocktrans %}</li>
<li>{% trans 'Click on "Leave project" in the bottom left corner' %}</li>
<li>{% trans 'Click on "Withdraw & revoke authorization"' %}</li>
</ul>
</li>
</ul>
</p>
{% endif %}
<p>
{% trans "Follow these steps to delete your data through the Open Humans website:" %}
<ul>
<li>
{% blocktrans %}Go to <a href="https://www.openhumans.org/activity/covid-open-survey/" target="_blank">https://www.openhumans.org/activity/covid-open-survey/</a>
{% endblocktrans %}
</li>
<li>{% trans 'Click on "Leave project" in the bottom left corner' %}</li>
<li>{% trans 'Check "Remove my data files for Covid Open Survey from Open Humans"' %}</li>
<li>{% trans 'Click on "Withdraw & revoke authorization"' %}</li>
</ul>
</p>
{% if request.user.is_authenticated %}
<p>
{% trans "You can also delete all your data files and your Covid Open Survey account:" %}
<br>
<a
href="{% url "delete_all" %}">{% trans "Click here to delete all your data files and your Covid Open Survey account" %}</a>.
</p>
{% endif %}
</div>
<h3 data-collapse aria-expanded="false">{% trans "Can others access my data?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
Other individual participants will not be able to access your data. However, the overall dataset will be made
available to the science community.
{% endblocktrans %}
</p>
</article>
<article>
<h2>{% trans "The survey" %}</h2>
<h3 data-collapse aria-expanded="false">{% trans "Why do I need to sign up with Open Humans?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
Open Humans hosts our secure servers, and is the place where all the data goes.
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "How do I end the survey?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
In your daily reminder to complete the survey, simply click on “I do not want to be part of this research
initiative
anymore” and we will stop sending you reminders.
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "Can someone under 18 enroll in the survey?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
No, this study is not open to participants under the age of 18
{% endblocktrans %}
</p>
<h3 data-collapse aria-expanded="false">{% trans "Whats the difference between Open Humans and OpenClinica?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
Open Humans encrypts and stores the data, OpenClinica is the survey platform: data is collected there and sent to
Open Humans.
{% endblocktrans %}
</p>
</article>
<article>
<h2>{% trans "The survey" %}</h2>
<h3 data-collapse aria-expanded="false">{% trans "I would like to help, what can I do?" %}
<svg xmlns="http://www.w3.org/2000/svg" height="12" width="20" viewbox="0 0 20 12" class="chevron">
<line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
<line x1="20" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="2" />
</svg>
</h3>
<p class="collapse__item">
{% blocktrans %}
The easiest way to help is by <strong>sharing the survey.</strong> If you want to get involved, send us an email at: <a href="mailto:contact@opencovid.org">contact@opencovid.org</a>
{% endblocktrans %}
</p>
</article>
</section>
{% endblock main %}

View File

@ -0,0 +1,322 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block intro %}
<section class="intro hp-intro">
<div class="intro-content intro-content--hp">
<h1>Covid-Open survey</h1>
{% if not request.user.is_authenticated %}
<p>
{% blocktrans %}
COVID-19 has turned our lives upside down. And while this virus continues to spread, millions around the world
are
taking positive action. From the comfort of your home, you too can join the global fight against this virus!
<strong>5 minutes a day.</strong> Thats all it takes to advance science.
{% endblocktrans %}
</p>
<p>
<strong>I want to help fight COVID-19!</strong>
</p>
<a class="button button--white" href="{{ request.openhumans_login_url }}">{% blocktrans %}Start the survey {% endblocktrans %}</a>
<a href="#content">{% blocktrans %}Or learn more before taking the survey.{% endblocktrans %}</a>
{% else %}
<p>
{% blocktrans %}
Thank you for taking part in this project! The team is grateful and eager for your participation.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
You can find your consent and email settings <a href="#settings">below.</a>
{% endblocktrans %}
</p>
{% endif %}
</div>
<div class="hp-intro__illustration">
<svg width="457" height="418" viewBox="0 0 457 418" xmlns="http://www.w3.org/2000/svg">
<title>Survey</title>
<desc>Ilustration of a survey sheet.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="a">
<stop stop-color="gray" stop-opacity=".25" offset="0%" />
<stop stop-color="gray" stop-opacity=".12" offset="54%" />
<stop stop-color="gray" stop-opacity=".1" offset="100%" />
</linearGradient>
<linearGradient x1="50%" y1="99.981618%" x2="50%" y2="0%" id="b">
<stop stop-color="gray" stop-opacity=".25" offset="0%" />
<stop stop-color="gray" stop-opacity=".12" offset="54%" />
<stop stop-color="gray" stop-opacity=".1" offset="100%" />
</linearGradient>
<linearGradient x1="50%" y1="100%" x2="50%" y2="-.024845%" id="c">
<stop stop-color="gray" stop-opacity=".25" offset="0%" />
<stop stop-color="gray" stop-opacity=".12" offset="54%" />
<stop stop-color="gray" stop-opacity=".1" offset="100%" />
</linearGradient>
</defs>
<g transform="translate(1)" fill="none" fill-rule="evenodd">
<path stroke="#163A40" fill="#FFF" fill-rule="nonzero" d="M128 31h202v280H128z" />
<path fill="url(#a)" fill-rule="nonzero" d="M180 24h100v25H180z" />
<path
d="M230 0c-7.732681 0-14 6.493015-14 14.5S222.267319 29 230 29s14-6.493015 14-14.5S237.732681 0 230 0zm0 22.864154c-3.363309.088915-6.444663-1.891606-7.793123-5.008981-1.34846-3.117376-.695229-6.750199 1.652101-9.187858 2.347331-2.437659 5.920852-3.194239 9.037876-1.913484 3.117024 1.280755 5.156214 4.343543 5.157363 7.746169.052778 4.556254-3.549254 8.296897-8.054217 8.364154z"
fill="url(#b)" fill-rule="nonzero" />
<path
d="M229.5 1c3.580423 0 7.0142 1.422317 9.545942 3.954058C241.577683 7.4858 243 10.919578 243 14.5c0 3.70519-1.492668 7.06175-3.909473 9.501148L279 24v23h-98V24l38.909473.001148C217.492668 21.56175 216 18.205191 216 14.5 216 7.044156 222.044156 1 229.5 1zm0 5.711538c-4.301449 0-7.788462 3.487014-7.788462 7.788462 0 4.301448 3.487013 7.788462 7.788462 7.788462s7.788462-3.487013 7.788462-7.788462c0-4.301448-3.487013-7.788462-7.788462-7.788462z"
stroke="#163A40" fill="#FFA318" fill-rule="nonzero" />
<path stroke="#163A40" fill="#35D78C" fill-rule="nonzero" d="M139 78h40v40h-40z" />
<path stroke="#163A40" fill="#FFA318" fill-rule="nonzero" d="M139 163h40v40h-40zM139 248h40v40h-40z" />
<path fill="#E0E0E0" fill-rule="nonzero"
d="M206 90h54v4h-54zM206 96h90v4h-90zM206 102h84v4h-84zM206 175h54v4h-54zM206 181h90v4h-90zM206 187h84v4h-84zM206 260h54v4h-54zM206 266h90v4h-90zM206 271h84v4h-84z" />
<path fill="url(#c)" fill-rule="nonzero"
d="M150.125 95.394783L157.625 102l10.625-15 3.75 3-14.375 18L147 96.6z" />
<path fill="#FFF" fill-rule="nonzero"
d="M151.625 96.2l6.3 5.657143L166.85 89l3.15 2.571429L157.925 107 149 97.228571zM151.625 181.2l6.3 5.657143L166.85 174l3.15 2.571429L157.925 192 149 182.228571zM151.625 266.2l6.3 5.657143L166.85 259l3.15 2.571429L157.925 277 149 267.228571z" />
<g stroke="#163A40" stroke-dasharray="11" stroke-width="5">
<path d="M424.5 171.5v-63M339.5 174.5h88" />
</g>
<path d="M119 96H49v67M160 322v38h105" stroke="#163A40" stroke-width="5" stroke-dasharray="12" />
<path stroke="#163A40" fill="#FFF" fill-rule="nonzero"
d="M455 100h-77V56.871972L416.238095 24 455 56.871972z" />
<path fill="#E0E0E0" fill-rule="nonzero" d="M379 57l37.741497 22L455 57" />
<path stroke="#979797" fill="#FAFAFA" fill-rule="nonzero