COVID’s report of Itabira city
By ppcamp
| 26 minutes read | 5527 wordsIn this page I’ll try to explain to you how to built a simple script that generates a refreshed report basing on some spreadsheet. In this case, this file was used by Itabira’s health secretary. The project can be found here.
Context
Since the first cases of the covid’s in Itabira, the Health Secretary ordered to generate a report to show to the citiziens what was happenning and the current situation of the city daily.

However, edit a report daily was an annoying task and tooked too much time and effort.
To “simplify” the process, I’ve created this script, which generates the daily report that was distribuited to the internal comittee and to the local journals.
You can download the full example of a PDF (with mocked data) right here or here.
Google Spreadsheets
First of all, you’ll need to get the authorization to the script access the spreadsheet. You’ll need to read about oauth2. Also, you’ll need to give the permissions to your credentials.
Why you need to have credentials? If you don’t create an api access/project, you’ll need to authorize your script access your spreadsheet by your web browser every single run, which isn’t scalable.
So, in the next lines, I’ll describe which every part does. From data acquisition to the generated pdf files.
Getting current date
In this step, I just got the current date formatted, which I’ll be using in the pdf’s header.
1# pytz.all_timezones to see all timezones
2from pytz import timezone # Defaul timezone it's ahead of Brasil(+)
3from datetime import datetime, date
4
5def isMonday():
6 return date.today().isoweekday() == 1
7
8# Note that changing linux localtime, doesn't affect Python
9BRASIL_TZ = timezone('America/Campo_Grande')
10DAY, MONTH, YEAR = datetime.now(BRASIL_TZ).strftime("%d %m %Y").split()
11
12def getMonthName(month, startUpper=False):
13 '''
14 Need it when execute in different system's locale.
15
16 Parameter
17 ---------
18 startUpper: (boolean)
19 Force to fst letter be upper.
20
21 Return
22 ------
23 (string) Month's names.
24 '''
25 month = int(month)
26 monthName = ['janeiro',
27 'fevereiro',
28 'março',
29 'abril',
30 'maio',
31 'junho',
32 'julho',
33 'agosto',
34 'setembro',
35 'outubro',
36 'novembro',
37 'dezembro'
38 ]
39 month = monthName[month-1] if month > 0 else monthName[-1]
40 m_up = month[0].upper() + month[1:]
41
42 return month if not startUpper else m_up
43
44# Get month's name
45MONTH_NAME = getMonthName(MONTH, True)
Setup logging
1import logging as log
2log.basicConfig(
3 format='[%(asctime)s] : %(levelname)s: %(funcName)s : %(message)s',
4 datefmt='%H:%M:%S',
5 filename='logs/LOG_{}-{}-{}.log'.format(YEAR,MONTH,DAY),
6 level=log.DEBUG)
7log.info('Logging created w/ success!')
8log.debug('Default timezone: {}'.format(BRASIL_TZ))
Load credentials and login

In this step, we need to get the spreadsheet data.
1from oauth2client.service_account import ServiceAccountCredentials as Credentials
2import gspread # Sheets
3
4# Login Constants
5SCOPE = ['https://www.googleapis.com/auth/spreadsheets.readonly']
6URL = '' # The spreadsheet url.
7# Note that you can use the environment variables instead
8# import os
9# os.environ.get("ENVIRON_SPREADSHEET_URL")
10
11# Create an Client
12GAUTH = Credentials.from_json_keyfile_name('credentials/nisis_credentials.json',SCOPE)
13# Auth the client
14GCLIENT = gspread.authorize(GAUTH)
15
16log.info('Client acquired with success!')
Sheet load and data treatment
Since we have load the data, and this data is filled by a Google’s Form, and there’s no pattern, some “errors” may occur.
So, to minimize those errors, we need to make some treatments, like stripping
the words, converting them to lower, removing empty rows, fixing wrong data
convertions (string
to int
)
1import pandas as pd
2from numpy import nan as NaN
3import numpy as np
4
5def getSheetsNames(URL, gc):
6 '''
7 Get all tab's names from url (sheet)
8
9 Parameters
10 ----------
11 URL: (str)
12 Url's sheet
13 gc: (GoogleClient)
14 Google client from gspread autenticated.
15
16 Return
17 ------
18 (list) Sheet's tab names.
19 '''
20 workSheets = GCLIENT.open_by_url(URL)
21 sheetsNames = [i.title for i in workSheets.worksheets()] # sheets names
22 return sheetsNames
23
24
25def getSheetValue(sheet_name, URL, gc, debug=False):
26 '''
27 Get pandas DF from sheet_name in WorkSheets.
28
29 Parameters
30 ----------
31 sheet_name: (string)
32 Name of sheet
33
34 Return
35 ------
36 (DataFrame) 2Dimensional sheet got it from one tab.
37
38 Examples
39 --------
40 >> all_dfs = list(map(getSheetValue,sheets))
41 >> list(map(lambda x: x.columns, all_dfs) )
42 >> df = pd.concat(all_dfs)
43 '''
44 wb = GCLIENT.open_by_url(URL) # client open this url
45 sheet = wb.worksheet(sheet_name) # get value(tab) settled from cell above
46 data = sheet.get_all_values() # get csv content from spreadsheet
47 df = pd.DataFrame(data) # convert csv into DataFrame
48
49 df.columns = df.iloc[0] # Remove Id's columns (They're in fst row)
50 df = df.iloc[1:] # Ignore fst row
51
52 # If we have an empty column
53 df.dropna(axis='columns')
54 # If we have an empty row
55 df.dropna(axis='rows')
56 # Drop fst column (Index)
57 df.drop(df.columns[0], axis=1, inplace=True)
58
59 if debug:
60 log.debug('Sem situação: ', len(list(
61 filter(lambda x: type(x) is not str, df['Selecione a situação:']))))
62
63
64 ############################ Treating data ##################################
65 # Renaming columns
66 # We rename column by column, 'cause if we do in this way, we won't have
67 # problems with insertion of new columns. If we made this by replacing columns
68 # list to a new one.
69 df.rename(columns={'Selecione a situação:': "Situation"}, inplace=True)
70 df.rename(columns={'Escolha a situação do caso confirmado:':"SituationOfConfirmed"}, inplace=True)
71 #df.rename(columns={'Houve internação?': "Id"}, inplace=True)
72 df.rename(columns={'Está monitorado pela central de vigilância da saúde? ': 'Monitoring'}, inplace=True)
73 df.rename(columns={'Houve internação?':'IsInterned'}, inplace=True)
74 df.rename(columns={'Sexo:':'Gender'}, inplace=True)
75 df.rename(columns={'Idade:':'Age'}, inplace=True)
76 df.rename(columns={'Bairro:':'Neighboorhood'}, inplace=True)
77 df.rename(columns={'Leito:':'HospitalClassifier'}, inplace=True)
78 df.rename(columns={'Escolha a situação do caso descartado:':'Discarted'}, inplace=True)
79 df.rename(columns={'Fatores de risco:':'RiskFactors'}, inplace=True)
80 df.rename(columns={'Semana epidemiológica':'EpidemicWeek'}, inplace=True)
81 df.rename(columns={'Data da internação:':'HospitalDate'}, inplace=True)
82
83 # Remove where Situation is empty -- In this dataframe, empty means that are duplicate
84 _filter = df['Situation'] != ''
85 df = df[_filter]
86
87 # Convert all dates in HospitalDate to format dd/mm/yyyy
88 # Note that we can't use Series.dt.date because we have
89 # empty fields as also 'PROFISSIONAL DE SAUDE', and furthermore
90 # 'FINALIZADO'.
91 def toFullYear(year):
92 if len(year)==8: # Means that are in format dd/mm/yy
93 year = year[:6] + '20' + year[6:]
94 return year
95 df['HospitalDate'] = df['HospitalDate'].apply(toFullYear)
96
97 # Convert to lower. We do this to minimize possible errors when making a string compare.
98 df['Situation'] = df['Situation'].str.lower()
99 df['Monitoring'] = df['Monitoring'].str.lower()
100 df['SituationOfConfirmed'] = df['SituationOfConfirmed'].str.lower()
101 df['HospitalClassifier'] = df['HospitalClassifier'].str.lower()
102 df['Gender'] = df['Gender'].str.lower()
103 df['RiskFactors'] = df['RiskFactors'].str.lower()
104 df['IsInterned'] = df['IsInterned'].str.lower()
105
106 # Fix: _nbh (empty and Spaced)
107 df['Neighboorhood'] = df['Neighboorhood'].str.strip() # Spaced
108 _filter = df['Neighboorhood'] == ''
109 df.loc[_filter, 'Neighboorhood'] = 'Sem Bairro' # Empty
110
111 # Fix: Convert str ages to int
112 _filter = df['Age'] == ''
113 df.loc[_filter, 'Age'] = 0 # put 0 in empty ages
114 df['Age'] = df['Age'].apply(lambda x: int(x)) # convert to int
115
116 # Fix: RiskFactors (empty and Spaced)
117 df['RiskFactors'] = df['RiskFactors'].str.strip() # Spaced
118 _filter = df['RiskFactors'] == ''
119 df.loc[_filter, 'RiskFactors'] = 'Não tem' # Empty
120
121 # Fix: Put N/A identifier in EpidemicWeek
122 _filter = np.array( df['EpidemicWeek'].str.isdigit(), dtype=np.bool)
123 df.loc[~_filter, 'EpidemicWeek'] = '#N/A' # Empty or #N/A
124 df.loc[_filter, 'EpidemicWeek'] = df.loc[_filter, 'EpidemicWeek'].apply(lambda x: int(x)) # Convert to int where is a number
125
126 df = df.reset_index(drop=True) # Drop removes old indexation
127
128 return df
1# Getting results
2sheetName = "{}-{}-{}".format(DAY,MONTH,YEAR)
3df = getSheetValue(sheetName, URL, GCLIENT)
4
5log.info('Tab "{}" oppened with success!'.format(sheetName))
Image and Copy imports
By now, we need to load the images that we’re gonna use in the pdf.
1# To import image in reportlab. Images are Pillow formats or BytesIO
2from reportlab.lib.utils import ImageReader
3
4from PIL import Image # Open png images
5from copy import deepcopy as dp # dataframe creation and manipulation permanent
We need to convert the image to white, otherwise it’ll use the black color instead.
1def alpha2white(img):
2 # Create a white rgb background
3 _img = Image.new("RGBA", img.size, "WHITE")
4 _img.paste(img, (0, 0), img)
5 _img.convert('RGB')
6 return _img.transpose(Image.FLIP_LEFT_RIGHT)
7
8# Draw method aim's to use ImageReader or path to object
9# we don't use here, 'cause of black background if alpha is 1
10boy = ImageReader( alpha2white(Image.open('img/boy.png').rotate(180)) )
11girl = ImageReader( alpha2white(Image.open('img/girl.png').rotate(180)) )
12logo = ImageReader( Image.open('img/logo.png').rotate(180).transpose(Image.FLIP_LEFT_RIGHT) )
13# It's necessary rotate because PIL inverted.
14
15log.info('Images loaded successfully')
Data analysis
Like I’ve said previously, this data hasn’t a pattern, therefore, we need to compare if the user misstyped, to avoid those errors, we use the string distance between two words. To do that, we use this, which is in fact a Natural Language Technique
1def similar(word1, word2, accept=False, caseSensitive=False, method='BuiltIn'):
2 '''
3 This method check similarity between strings. It can be used with
4 two ways. Using built-in method or leveinshtein implementation by
5 Antti Haapala. If use leveinshtein, need to
6 >>> pip install python-Levenshtein
7 See
8 ---
9 https://rawgit.com/ztane/python-Levenshtein/master/docs/Levenshtein.html
10
11 Parameters
12 ----------
13 word1: (string) To compare
14 word2: (string) To compare
15 accept: (int) If 0 will return percentual, else return true for value in percent
16 caseSensitive: (bool) Set false to disable
17 method: (string) 'BuiltIn' or 'Levenshtein'
18
19 Return
20 ------
21 Similarity in percentual if accept is False, otherwise,
22 True for percentual > accept
23 '''
24
25 if not caseSensitive:
26 word1 = word1.lower()
27 word2 = word2.lower()
28
29 if method == 'BuiltIn':
30 from difflib import SequenceMatcher
31 percent = SequenceMatcher(None, word1, word2).ratio()
32 return percent if not accept else percent>=accept
33
34 elif method == 'Levenshtein':
35 from Levenshtein import ratio
36 percent = ratio(word1, word2)
37 return percent if not accept else percent>=accept
38
39 else:
40 raise(Exception('Method not implemented.'))
41
42def applyFilter(df, l, word, col):
43 '''
44 Check in "col" of "l" situation, the amount of word who matches with "word"
45 with 0.7 similarity.
46
47 Parameters
48 ----------
49 df: (DataFrame) Data to analysis
50 l: (list) Bitset with lines to analysis.
51 word: (string) Word to analysis
52 col: (string) Column to search for.
53
54 Return
55 ------
56 (int) Amount of ocurrences.
57
58 Example
59 -------
60 >> df = pd.DataFrame(['teste']*8, columns={'c1'})
61 col1
62 * 0 1.0
63 * 1 1.0
64 * 2 1.0
65 3 1.0
66 4 1.0
67 5 1.0
68 6 1.0
69 * 7 1.0
70 >> applyFilter(df, 3*[1]+4*[0]+[1], 'teste', 'c1')
71 4
72 >> 3*[1]+4*[0]+[1] == [1 1 1 0 0 0 0 1]
73 '''
74
75 def getValue(x):
76 if type(x) is not str:
77 return 0
78 else:
79 return similar(x,word,0.7)
80 return int(len(list( filter(getValue, df.loc[l, col]) )))
To manipulate even more the data, we use a numpy array, which allow us to make bitwise operations in the vector.
1# To be clear in variable manipulation, every var in this section will have
2# an d2a_ prefix (data to analysis). If it's a const will be UPPER_CASE
3
4AGES = [
5 'Não informado',
6 '1 a 9',
7 '10 a 19',
8 '20 a 29',
9 '30 a 39',
10 '40 a 49',
11 '50 a 59',
12 '>= 60'
13]
14
15# Risk factors
16DISEASES = [
17 'Doenças respiratórias crônicas descompensadas',
18 'Doenças cardíacas crônicas',
19 'Diabetes',
20 'Doenças renais crônicas em estágio avançado (graus 3,4 e 5)',
21 'Imunossupressão',
22 'Gestantes de alto risco',
23 'Portadores de doenças cromossômicas ou em estados de fragilidade imunológica (ex:.Síndrome de Down)',
24 'Não tem',
25 'Outros'
26]
27
28
29# Give a vector position according to AGES list
30def ageRange(age):
31 if age<1: return 0
32 if age>=1 and age<10: return 1
33 if age>=10 and age<20: return 2
34 if age>=20 and age<30: return 3
35 if age>=30 and age<40: return 4
36 if age>=40 and age<50: return 5
37 if age>=50 and age<60: return 6
38 return 7
39
40# Convert to percent
41def percentage(number, total):
42 return round(number*100/total, 1)
43
44
45# Vector of positions in dataframe corresponding to situations
46d2a_vConfirmed = np.array( df['Situation']=='confirmado', dtype=np.bool )
47d2a_vDunderI = np.array( df['Monitoring']=='obito em investigacao', dtype=np.bool ) # deaths under investigation
48d2a_vSuspect = np.array( df['Situation']=='suspeito', dtype=np.bool ) | d2a_vDunderI
49d2a_vDiscarted = np.array( df['Situation']=='descartado', dtype=np.bool )
50d2a_vCinterned = np.array( df['SituationOfConfirmed']=='internado', dtype=np.bool ) # Only for those confirmed interned
51d2a_vInterned = np.array( df['IsInterned']=='sim', dtype=np.bool ) # suspects, confirmed, discarted etc
52# After talk to Patrícia, she told me that she itself change those ones who are
53# interned, so, we actually must ignore those informations in google forms, and attempt
54# just to search of values in fields
55# Only for those suspects interned
56d2a_vSinterned = np.array( df['Monitoring']=='internado', dtype=np.bool ) & d2a_vSuspect # We can't use loc, cause it will raise only true ones
57d2a_vHospitals = np.array( df['Hospital'].str.len() > 4, dtype=np.bool) # Find hospitals with str lenght > 4 (Belo Horizonte)
58d2a_vMonitoring = np.array(df['Monitoring']=='sim', dtype=np.bool ) # Those that are not in hospital
59
60# Total of ..
61d2a_TofConfirmed = np.count_nonzero(d2a_vConfirmed) # Confirmed cases
62d2a_TofDiscarted = np.count_nonzero(d2a_vDiscarted) # Discarted cases
63d2a_TofSuspect = np.count_nonzero(d2a_vSuspect) # Suspects cases
64d2a_TofDunderI = np.count_nonzero(d2a_vDunderI) # Total of deaths under inestigation
65
66# Total of Confirmed in ..
67_vBH, _vITA = d2a_vCinterned&d2a_vHospitals, d2a_vCinterned&~d2a_vHospitals # Auxiliar vectors of hospital and Confirmed interneds
68d2a_TofCnurseryBH = applyFilter(df, _vBH,'enfermaria','HospitalClassifier') # in nursery in BH
69d2a_TofCnurseryITA = applyFilter(df, _vITA,'enfermaria','HospitalClassifier') # in nursery in ita
70d2a_TofCuti = applyFilter(df, _vITA,'ti','HospitalClassifier') # in uti in Ita
71d2a_TofCcti = applyFilter(df, _vBH,'ti','HospitalClassifier') # in uti in BH
72d2a_TofCrecover = applyFilter(df, d2a_vConfirmed,'recuperado','SituationOfConfirmed') # that recovered
73d2a_TofChome = applyFilter(df, d2a_vConfirmed,'amento domiciliar','SituationOfConfirmed') # that are in home
74d2a_TofCdead = applyFilter(df, d2a_vConfirmed,'óbito','SituationOfConfirmed') # that are dead
75d2a_TofCfemale = applyFilter(df, d2a_vConfirmed,'f','Gender') # were gender is
76d2a_TofCmale = applyFilter(df, d2a_vConfirmed,'m','Gender') # were gender is
77d2a_TofCdiseases = [applyFilter(df, d2a_vConfirmed, it,'RiskFactors') for it in DISEASES]
78_aux = sum(d2a_TofCdiseases)
79d2a_TofCdiseases = [percentage(it,_aux) for it in d2a_TofCdiseases] # according with diseases (in percentage)
80
81# Total of Suspects in ...
82d2a_TofSmonitor = np.count_nonzero(d2a_vMonitoring & d2a_vSuspect) # monitoring
83d2a_TofSnursery = applyFilter(df, d2a_vSinterned,'enfermaria','HospitalClassifier') # in hospital (nursery)
84d2a_TofSuti = applyFilter(df, d2a_vSinterned,'uti','HospitalClassifier') # in hospital (uti)
85d2a_TofSfemale = applyFilter(df, d2a_vSuspect,'f','Gender') # were gender is
86d2a_TofSmale = applyFilter(df, d2a_vSuspect,'m','Gender') # were gender is
87
88
89# Total of (Suspects and Confirmed) according with diseases (in percentage)
90d2a_TofCSdiseases = [applyFilter(df, d2a_vConfirmed | d2a_vSuspect, it,'RiskFactors') for it in DISEASES]
91_aux = sum(d2a_TofCSdiseases)
92d2a_TofCSdiseases = [percentage(it,_aux) for it in d2a_TofCSdiseases]
93
94
95# Total of Discarted deaths
96d2a_TofDdeaths = applyFilter(df, d2a_vDiscarted,'óbito','Discarted')
97
98
99
100# Create Vectors of Ages corresponding of situations
101d2a_vSage = [0]*len(AGES) # Suspect ages
102d2a_vCage = [0]*len(AGES) # Confirmed
103
104# Week
105d2a_vCSweekName = list(set(df['EpidemicWeek']))
106d2a_vCSweekName.remove('#N/A') # With this we force '#N/A' to be the first in list
107d2a_vCSweekName = ['#N/A'] + d2a_vCSweekName # we use this trick to force positions below
108d2a_vCSweekValue = [0]*len(d2a_vCSweekName) # same idea as ages
109
110# Populate vectors of week and ages
111for it in df.index:
112 if d2a_vSuspect[it]:
113 d2a_vSage[ ageRange( df.loc[it,'Age'] ) ] += 1
114 _week = df.loc[it, 'EpidemicWeek']
115 if _week =='#N/A':
116 _week = 0
117 # force position from set above (start to count at 10th week)
118 d2a_vCSweekValue[_week-9 if _week>0 else 0] += 1
119
120 if d2a_vConfirmed[it]:
121 d2a_vCage[ ageRange(df.loc[it,'Age']) ] += 1
122 _week = df.loc[it, 'EpidemicWeek']
123 if _week =='#N/A':
124 _week = 0
125 # Note that week value to"1'\n".strip()ok both situations.
126 d2a_vCSweekValue[_week-9 if _week>0 else 0] += 1
127
128# Where don't have an NHD the type is NaN, due that, we can't access it
129_nbh = []
130for i in set(df['Neighboorhood']):
131 conf = len(list(filter(lambda x: x, df.loc[d2a_vConfirmed, 'Neighboorhood']==i)))
132 susp = len(list(filter(lambda x: x, df.loc[d2a_vSuspect, 'Neighboorhood']==i)))
133 #anali = len(list(filter(lambda x: x, df.loc[d2a_vAnalysis, 'Neighboorhood']==i)))
134 _nbh.append({ 'Neighboorhood': i, 'qtdSuspect': susp, 'qtdConf': conf})#, 'qtdAnalis': anali})
135# Sort by ascending order
136d2a_vNeighboorhood = sorted(_nbh, key=lambda k: k['qtdConf'], reverse=True)
137_aux = [i for i in d2a_vNeighboorhood if i['qtdSuspect'] or i['qtdConf']]
138d2a_vNeighboorhood = _aux
139
140# Now we must access a stored data which refers to oldiest reports
141# This is needed, because the sheetsheet change the current situation
142# over time, losing the oldiest suspects, e.g.
143_cdate = '{}-{}-{}'.format(YEAR, MONTH, DAY)
144
145# Import database's library
146import sqlite3
147
148# Connect to sqlite database used to store the data of the past mondays
149database = sqlite3.connect("others/database.sqlite")
150
151# Load the data into a DataFrame
152d2a_dfCStimeline = pd.read_sql_query(
153 "SELECT * from Covid_and_Suspects_timeline",
154 database,
155 index_col="Id"
156)
157
158# Update the database if it's monday
159if isMonday():
160 # If the date already exist in df, update it
161 if _cdate in d2a_dfCStimeline['Data'].tolist():
162 _pos = d2a_dfCStimeline['Data']==_cdate
163 d2a_dfCStimeline.loc[_pos, 'Sindrome'] = np.int64(d2a_TofSuspect)
164 d2a_dfCStimeline.loc[_pos, 'Covid'] = np.int64(d2a_TofConfirmed)
165 # If don't (runned at first time in the monday), create a new row
166 else:
167 d2a_dfCStimeline = d2a_dfCStimeline.append({
168 'Data': _cdate,
169 'Sindrome': np.int64(d2a_TofSuspect),
170 'Covid': np.int64(d2a_TofConfirmed)
171 },
172 ignore_index=True)
173
174
175# Write the dataframe d2a_dfCStimeline to the database
176d2a_dfCStimeline.to_sql(
177 "Covid_and_Suspects_timeline", # Table name
178 database, # Database name
179 if_exists="replace", # Replace table if exist
180 index_label="Id", # Index name
181 index=True # Enable index name
182)
183
184# Close DB connection
185database.close()
Plots
After get all data that will be printed in the pdf, we need to generate the plots to make that, we use the Seaborn library, which in fact, just makes the plot “pretty” without needing to setup every single config.
1import matplotlib.pyplot as plt
2import seaborn as sns # Change color plot
3from io import BytesIO # Image buff
4
5sns.set_style('darkgrid')
6
7def barPlot(x,y, siz, palette="Oranges"):
8 fig = plt.figure( figsize=siz )
9
10 plt.tick_params(
11 axis='x', # changes apply to the x-axis
12 which='both', # both major and minor ticks are affected
13 bottom=False, # ticks along the bottom edge are off
14 labelbottom=False) # labels along the bottom edge are off
15 ax = sns.barplot(x=x, y=y, palette=palette)
16
17 _currSpace = ax.get_xticks()
18 _currSpace = _currSpace[1] - _currSpace[0]
19 plt.xticks(np.arange(0, max(x)+_currSpace, _currSpace))
20
21 # Create annotions marks using total amount of infected
22 maxX = max(x)
23 _y = 0
24 for i in x:
25 ax.text(i + maxX/100, _y, str(i))
26 _y+=1
27
28 fig.tight_layout() # Remove extra paddings
29 # Convert fig img to buffer img
30 buff = BytesIO()
31 fig.savefig(buff, format='PNG')
32 buff.seek(0)
33 buff = Image.open(buff).rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
34 return ImageReader( buff )
35
36
37def linePlot(y,x, siz,palette="Oranges"):
38 fig = plt.figure( figsize=siz )
39 plt.plot(x,y, marker='o', color='darkred', linewidth=2, markersize=12)
40 # sns.set_palette(palette)
41
42 fig.tight_layout() # Remove extra paddings
43 # Convert fig img to buffer img
44 buff = BytesIO()
45 fig.savefig(buff, format='PNG')
46 buff.seek(0)
47 buff = Image.open(buff).rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
48 return ImageReader( buff )
49
50
51def linePlot2(df, y, siz, ftsize='small'):
52 from math import floor
53
54 fig = plt.figure( figsize=siz )
55
56 plt.plot(
57 range(0, df.shape[0]),
58 df[y].tolist(),
59 marker='o',
60 color='darkred',
61 linewidth=2,
62 markersize=5
63 )
64
65 spacingY = max(df[y])/10
66 xStart = 0
67 xSpace = floor( (df.shape[0] - xStart)/10 ) # It'll give me the spacing
68 vxSpace = [i for i in range(xStart, df.shape[0], 1) ]
69 vxNames = []
70
71 # Convert into word like
72 for i in df.loc[ vxSpace, 'Data' ].tolist():
73 _c = i.split('-')
74 vxNames.append("{}/{}".format(_c[-1], getMonthName(_c[1])) )
75
76 plt.xticks(vxSpace, vxNames, rotation=90)
77
78 # Put number y values as annotations
79 for i in range(df.shape[0]):
80 _v = df.loc[i,y]
81 plt.text(
82 i,
83 _v+spacingY,
84 str(_v),
85 fontsize=ftsize,
86 rotation=90,
87 verticalalignment='baseline',
88 horizontalalignment='center'
89 )
90
91 # Grid Y positions numbers
92 maxY = max(df[y])
93 plt.yticks(
94 range(0,maxY+300, 100)
95 )
96
97 fig.tight_layout() # Remove extra paddings
98 # Convert fig img to buffer img
99 buff = BytesIO()
100 fig.savefig(buff, format='PNG')
101 buff.seek(0)
102 buff = Image.open(buff).rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
103 return ImageReader( buff )
1# Generating
2graphic_C = barPlot(d2a_vCage, AGES, (7,5))
3graphic_S = barPlot(d2a_vSage, AGES, (7,5))
4graphic_Cdiseases = barPlot(d2a_TofCdiseases, DISEASES, (15,5))
5graphic_CSdiseases = barPlot(d2a_TofCSdiseases, DISEASES, (15,5))
6graphic_CSweek = linePlot(d2a_vCSweekValue, d2a_vCSweekName, (15,5))
7graphic_Ctimeline = linePlot2(d2a_dfCStimeline, 'Covid', (15,5) )
8graphic_Stimeline = linePlot2(d2a_dfCStimeline, 'Sindrome', (15,5) )
PDF report
Imports
1from reportlab import __version__
2# Canvas are used to draw in pdf (When U won't to create a document template)
3from reportlab.pdfgen import canvas
4# Tool to create colors models in reportlab. Colors as weel
5from reportlab.lib import colors as rlabColors
6# Tools to import family fonts
7from reportlab.pdfbase import pdfmetrics
8from reportlab.pdfbase.ttfonts import TTFont
Font family and others settings
1################ Font family and Colors ########################
2# https://fontawesome.com/cheatsheet/free/solid
3pdfmetrics.registerFont(TTFont('FontAwesomeS', 'fonts/FontAwesome_5_' + 'Solid.ttf'))
4pdfmetrics.registerFont(TTFont('FontAwesomeB', 'fonts/FontAwesome_5_' + 'Brands.ttf'))
5pdfmetrics.registerFont(TTFont('Montserrat','fonts/Montserrat-'+'Regular.ttf'))
6pdfmetrics.registerFont(TTFont('Montserratb','fonts/Montserrat-'+'Bold.ttf'))
7pdfmetrics.registerFont(TTFont('Montserrati','fonts/Montserrat-'+'Italic.ttf'))
8pdfmetrics.registerFont(TTFont('Montserratbi','fonts/Montserrat-'+'BoldItalic.ttf'))
9pdfmetrics.registerFontFamily(
10 'Montserrat',
11 normal='Montserrat',
12 bold='Montserratb',
13 italic='Montserrati',
14 boldItalic='Montserratbi')
15
16# Colors not def'ed in rlabColors
17myColors = {
18 'HeadOrange': rlabColors.toColor('rgb(209,64,19)'),
19 'Head2Orange': rlabColors.toColor('rgb(255,160,153)'),
20 'HeadBlue': rlabColors.toColor('rgb(20,13,93)'),
21 'Head2Blue': rlabColors.toColor('rgb(0,171,153)'),
22 'BlueGray': rlabColors.toColor('rgb(195,210,231)'),
23 'Green': rlabColors.toColor('rgb(179,111,90)'),
24 'GreenD': rlabColors.toColor('rgb(0,255,0)'),
25 'BlueFB': rlabColors.toColor('rgb(9,37,83)'),
26 'IceWhite': rlabColors.toColor('rgb(233,233,233)')
27}
Default configs
1keywords = ['PDF report','Corona', 'Corona vírus', 'vírus', 'COVID19']
2progVers = '2.0'
3author = 'Pedro Augusto C Santos'
4subject = 'NISIS - SMS de Itabira'
5creator = 'ReportLab v'+__version__
6producer = 'www.reportlab.com'
7
8xPos = 0
9yPos = 0
10page = '' # just to initialize the variable
11
12# To the settings before I just need a way to propagate changes
13# without rewrite everything. Furthermore, I'm using the characteristics
14# of python, to search variables defined before to minimize the number of
15# parameters that I would to put in Class, or either in a function.
PDF generic functions
The reportlab library, is a great tool, however, in its free version, we don’t have a great support for hyperlinks in the text (usually it don’t works). To fix that, we make a quickfix, using the native hyperlink method and setting up the hyperlink block.
1def pdfDrawLink(url, x, y, width, height, color=False):
2 '''
3 This function fix the link problem with the coordinates system.
4
5 Globals
6 -------
7 page: (reportlab Canvas) Pdf itself.
8 pgDim: (dict) 'w'=page_width, 'h'=page_height.
9
10 Parameters
11 ----------
12 url: (str) Link to webpage. Consider to use "https://{}".format(url),
13 otherwise, will try to link with file.
14 x: (int) X position.
15 y: (int) Y position.
16 width: (int) Size dimension.
17 height: (int) Size dimension.
18 color: (bool) If true, draws a rectangle equivalent to the area where the link are.
19 '''
20 global page, pgDim
21
22 if color:
23 page.setFillColor(myColors['Green'])
24 page.rect(x,y,width,height,fill=1,stroke=0)
25
26 y = pgDim['h'] - y
27
28 page.linkURL(
29 url,
30 (x, y, x+width, y-height),
31 thickness=0
32 # relative=1, # This doesn't nothing.
33 # In theory, this should be capable to use page properties instead default coordinate system (bottom up, left right)
34 )
Start
The first pdf setup, and its meta informations.
1def pdf_Start(fileName):
2 global pgDim, xPos, yPos, page
3
4 xPos = 0
5 yPos = 0
6
7 page = canvas.Canvas(
8 fileName,
9 pagesize=(pgDim['w'],pgDim['h']),
10 bottomup = 0,
11 pageCompression=0,
12 verbosity=0,
13 encrypt=None
14 )
15 page.setProducer(producer)
16 page.setKeywords(keywords)
17 page.setCreator(creator)
18 page.setAuthor(author)
19 page.setSubject(subject)
Title
Draws the bigger text
1def setTitle(t):
2 global page
3
4 page.setTitle(t)
5 page.setFillColor(rlabColors.white)
Header
1def putHeader(c1, c2):
2 global page, yPos
3
4 # Header
5 page.setFillColor(myColors[c1])
6 page.rect(0,0,pgDim['w'], 148, stroke=0, fill=1)
7 # Sub header
8 page.setFillColor(myColors[c2])
9 page.rect(0,148,pgDim['w'], 47, stroke=0, fill=1)
10
11 page.setFont("Montserrat",36)
12 page.setFillColor(rlabColors.white)
13 page.drawCentredString(
14 pgDim['w']/2,
15 105.2,
16 "BOLETIM EPIDEMIOLÓGICO"
17 )
18
19 page.setFont("Montserrat",13)
20 page.drawCentredString(
21 pgDim['w']/2,
22 130.6,
23 "COVID-19: Doença causada pelo Novo Coronavírus"
24 )
25
26 page.setFont("Montserratb",13)
27 page.drawString(
28 13,
29 173.6,
30 "{} de {} de {}".format(DAY,MONTH_NAME,YEAR)
31 )
32
33 page.drawString(
34 570,
35 173.6,
36 "Secretaria de Saúde de Itabira"
37 )
38
39 yPos = 200 # 195
40 # Before this point, everything must use yPos parameter to position Y coords
Emphasis
1def putEmphasis():
2 global page, yPos
3
4 def dots(page, x, y, s, k):
5 '''
6 Parameters
7 ----------
8 page: (reportlab) (where to modify)
9 x: (int) x start pos
10 y: (int) y start pos
11 s: (int) y number of dots
12 x: (int) x number of dots
13 '''
14 i,j = 1,0
15 while i <= s:
16 page.circle(x, y + 8*i, 2, stroke=0, fill=1)
17 i+=1
18 while j <= k:
19 page.circle(x + 8*j, y + 8*i, 2, stroke=0, fill=1)
20 j+=1
21
22 # Rectangles and Dots (LEFT)
23 page.setFillColor(myColors['BlueFB'])
24 page.roundRect(14, yPos + 75,219, 112, 15, 0, 1)
25 dots(page, 30, yPos + 187, 6, 1)
26 dots(page, 30, yPos + 235, 6, 3)
27 dots(page, 30, yPos + 235+48, 11, 3)
28 page.setFont('Montserrat',18)
29 page.drawString(115, yPos + 239,'PESSOA(S) EM')
30 page.drawString(115, yPos + 255,'MONITORAMENTO')
31 page.drawString(115, yPos + 285, 'PESSOA(S)')
32 page.drawString(115, yPos + 301, 'HOSPITALIZADA(S)')
33 page.drawString(115, yPos + 239+48+88,'ÓBITO(S) EM')
34 page.drawString(115, yPos + 255+48+88,'INVESTIGAÇÃO')
35 page.setFont('Montserrat',32)
36 page.drawCentredString(75, yPos + 251, str(d2a_TofSmonitor))
37 page.drawCentredString(75, yPos + 300, str(d2a_TofSnursery + d2a_TofSuti))
38 page.drawCentredString(75, yPos + 300+88, str(d2a_TofDunderI))
39 page.setFont('Montserrat',18)
40 page.setFillColor(rlabColors.gray)
41 page.drawCentredString(108, yPos + 327, str(d2a_TofSnursery))
42 page.drawCentredString(108, yPos + 345, str(d2a_TofSuti))
43 page.setFont('Montserrat',12)
44 dots(page, 70, yPos + 300, 2, 2)
45 dots(page, 70, yPos + 316, 2, 2)
46 page.drawString(98+20, yPos + 325, 'enfermaria')
47 page.drawString(98+20, yPos + 343, 'em UTI')
48
49 # Rectangles and DOTS (MIDDLE)
50 page.setFillColor(rlabColors.red)
51 page.roundRect(288 - 10, yPos + 75, 219, 112, 15, 0, 1)
52 dots(page, 330 - 10, yPos + 187, 4, 2)
53 dots(page, 330 - 10, yPos + 187, 8, 2)
54 dots(page, 330 - 10, yPos + 187, 12, 2)
55 dots(page, 330 - 10, yPos + 187, 16, 2)
56 dots(page, 330 - 10, yPos + 187, 20, 2)
57 dots(page, 330 - 10, yPos + 187, 24, 2)
58 dots(page, 330 - 10, yPos + 187, 28, 2)
59 page.setFont('Montserrat',32)
60 page.drawCentredString(395 - 20, yPos + 240, str(d2a_TofCrecover))
61 page.drawCentredString(395 - 20, yPos + 270, str(d2a_TofChome))
62 page.drawCentredString(395 - 20, yPos + 300, str(d2a_TofCdead))
63 page.drawCentredString(395 - 20, yPos + 335, str(d2a_TofCnurseryITA))
64 page.drawCentredString(395 - 20, yPos + 365, str(d2a_TofCuti))
65 page.drawCentredString(395 - 20, yPos + 395, str(d2a_TofCnurseryBH))
66 page.drawCentredString(395 - 20, yPos + 395+30, str(d2a_TofCcti))
67 page.setFont('Montserrat',12)
68 page.setFillColor(rlabColors.gray)
69 page.drawString(428 - 10, yPos + 230, 'recuperado(s)')
70 page.drawString(428 - 10, yPos + 255, 'em isolamento domiciliar') #
71 page.drawString(448 - 10, yPos + 268, 'monitorado') #
72 page.drawString(428 - 10, yPos + 293, 'óbito(s) confirmado(os)')
73 page.drawString(428 - 10, yPos + 325, 'hospitalizado(s) em enfermaria em Itabira')
74 page.drawString(428 - 10, yPos + 355, 'hospitalizado(s) em UTI em Itabira')
75 page.drawString(428 - 10, yPos + 385, 'hospitalizado(s) em enfermaria em outra cidade')
76 page.drawString(428 - 10, yPos + 385+30, 'hospitalizado(s) em UTI em outra cidade')
77
78
79 # Rectangles and DOTS (RIGHT)
80 page.setFillColor(myColors['GreenD'])
81 page.roundRect(563, yPos + 75, 219, 112, 15, 0, 1)
82 dots(page, 590 + 15, yPos + 212, 5, 1)
83 page.setFont('Montserrat',32)
84 page.drawCentredString(620 + 15, yPos + 272, str(d2a_TofDdeaths))
85 page.setFont('Montserrat',12)
86 page.setFillColor(rlabColors.gray)
87 page.drawString(563 + 15, yPos + 200, 'Testaram negativo para Covid-19')
88 page.drawString(573 + 15, yPos + 210, 'ou positivo para outra doença')
89 page.drawString(635 + 20, yPos + 265, 'óbito(s) descartados')
90
91
92 # Text over rectangles
93 page.setFont("Montserrat",18)
94 page.setFillColor(rlabColors.gray)
95 page.drawCentredString(
96 14 + 219/2,
97 178 + 3.6*12,
98 "Notificações de"
99 )
100 page.drawCentredString(
101 14 + 219/2,
102 178 + 3.6*17,
103 "Síndrome Respiratória"
104 )
105 page.drawCentredString(
106 14 + 219/2,
107 178 + 3.6*22,
108 "não específicada"
109 )
110 page.drawCentredString(
111 288 + 219/2,
112 178 + 3.6*17,
113 "Casos Confirmados"
114 )
115 page.drawCentredString(
116 563 + 219/2,
117 178 + 3.6*17,
118 "Casos Descartados"
119 )
120
121 # Text inside rectangles
122 page.setFont("Montserratbi",48)
123 page.setFillColor(rlabColors.white)
124 page.drawCentredString(
125 123.5,
126 yPos + 153.6,
127 str(d2a_TofSuspect)
128 )
129 page.drawCentredString(
130 397.5,
131 yPos + 153.6,
132 str(d2a_TofConfirmed)
133 )
134 page.drawCentredString(
135 672.5,
136 yPos + 153.6,
137 str(d2a_TofDiscarted)
138 )
139
140 yPos = 700
141
Perfil Epidemiológico Dos Casos De Síndrome Respiratória Não Específicada
1def putSecOne():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 "PERFIL EPIDEMIOLÓGICO DOS CASOS DE SÍNDROME")
11 page.drawCentredString(
12 pgDim['w']/2,
13 yPos + 30,
14 "RESPIRATÓRIA NÃO ESPECÍFICADA")
15
16 # Subtitle
17 page.setFont("Montserrat",14)
18 page.setFillColor(rlabColors.gray)
19 page.drawCentredString(
20 165,
21 yPos + 68,
22 "Por sexo")
23 page.drawCentredString(
24 555,
25 yPos + 68,
26 "Por faixa etária")
27
28 # Draw boy, girl and plot
29 page.drawImage(boy, 190, yPos + 88, 35,114)
30 page.drawImage(girl, 101, yPos + 88, 40,114)
31 page.drawImage(graphic_S, 400, yPos + 88, 350,250)
32
33 # Draw boy and girl numbers
34 page.setFont("Montserrat",14)
35 page.setFillColor(rlabColors.gray)
36
37 page.drawCentredString(
38 120,
39 yPos + 240,
40 str(d2a_TofSfemale))
41 page.drawCentredString(
42 207,
43 yPos + 240,
44 str(d2a_TofSmale))
45
46 yPos += 450
Perfil epidemiológico casos confirmados
1def putSecTwo():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 "PERFIL EPIDEMIOLÓGICO DOS CASOS CONFIRMADOS")
11
12 # Subtitle
13 page.setFont("Montserrat",14)
14 page.setFillColor(rlabColors.gray)
15 page.drawCentredString(
16 165,
17 yPos + 68,
18 "Por sexo")
19 page.drawCentredString(
20 555,
21 yPos + 68,
22 "Por faixa etária")
23
24 # Draw boy, girl and plot
25 page.drawImage(boy, 190, yPos + 88, 35,114)
26 page.drawImage(girl, 101, yPos + 88, 40,114)
27 page.drawImage(graphic_C, 400, yPos + 88, 350,250)
28
29 # Draw boy and girl numbers
30 page.setFont("Montserrat",14)
31 page.setFillColor(rlabColors.gray)
32 page.drawCentredString(
33 120,
34 yPos + 240,
35 str(d2a_TofCfemale))
36 page.drawCentredString(
37 207,
38 yPos + 240,
39 str(d2a_TofCmale))
40
41 yPos += 450
Distribuição bairros
1def putSecThree():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 "DISTRIBUIÇÃO DOS CASOS POR BAIRRO")
11
12 yPos += 50
13 xPos = 30
14 multiplier = 210 # column distance
15
16
17 # Neighboor column names
18 page.setFillColor(rlabColors.gray)
19 page.setFont("Montserratbi",10)
20 page.drawString(xPos, yPos, "Bairros")
21 page.drawCentredString(xPos + 2.7*multiplier, yPos, "Casos de síndrome respiratória não especificada")
22 # page.drawCentredString(xPos + 2*multiplier, yPos, "Baixa Probabilidade")
23 page.drawCentredString(xPos + 1.5*multiplier, yPos, "Casos confirmados")
24
25 # Drawing neighboorhood in pdf
26 page.setFont("Montserratb",10)
27 yPos += 5
28 for i in d2a_vNeighboorhood:
29 yPos+= 17
30 page.drawString(xPos, yPos, i['Neighboorhood'])
31 page.drawCentredString(xPos + 2.7*multiplier, yPos, str(i['qtdSuspect']))
32 #page.drawCentredString(xPos + 2*multiplier, yPos, str(i['qtdAnalis']))
33 page.drawCentredString(xPos + 1.5*multiplier, yPos, str(i['qtdConf']))
34
35 # Drawing total of analysis
36 yPos += 5
37 page.drawString(xPos, yPos+17, 'Total')
38 page.drawCentredString(xPos + 2.7*multiplier, yPos + 17, str(sum(item['qtdSuspect'] for item in d2a_vNeighboorhood)))
39 #page.drawCentredString(xPos + 2*multiplier, yPos + 17, str(sum(item['qtdAnalis'] for item in d2a_vNeighboorhood)))
40 page.drawCentredString(xPos + 1.5*multiplier, yPos + 17, str(sum(item['qtdConf'] for item in d2a_vNeighboorhood)))
41
42 yPos += 100
Síndrome Respiratória Não Especificada Por Fator De Risco
1def putSecFour():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 'SÍNDROME RESPIRATÓRIA NÃO ESPECIFICADA')
11 yPos += 30
12 page.drawCentredString(
13 pgDim['w']/2,
14 yPos,
15 'POR FATOR DE RISCO')
16 yPos += 30
17
18 # Draw graphic disease
19 _dist = 20
20 page.drawImage(
21 graphic_CSdiseases,
22 _dist,
23 yPos,
24 pgDim['w']-2*_dist,
25 (pgDim['w']-2*_dist)/3
26 )
27 yPos += 350
Fator de risco confirmados
1def putSecFive():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 'CASOS CONFIRMADOS POR FATOR DE RISCO')
11 yPos += 30
12 page.drawCentredString(
13 pgDim['w']/2,
14 yPos,
15 "(EM PORCENTAGEM)")
16 yPos += 30
17
18 # Draw graphic disease
19 _dist = 20
20 wh_proportion = 1/3 # this proportion is, approximately, the same as figure ploting
21 page.drawImage(
22 graphic_Cdiseases,
23 _dist,
24 yPos,
25 pgDim['w']-2*_dist,
26 (pgDim['w']-2*_dist)/3
27 )
28 yPos += 350
Semana epidemiológica
1def putSecSix():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 'SÍNDROME RESPIRATÓRIA NÃO ESPECIFICADA')
11 yPos += 30
12 page.drawCentredString(
13 pgDim['w']/2,
14 yPos,
15 'POR SEMANA EPIDEMIOLÓGICA')
16 yPos += 30
17
18 # Draw graphic disease
19 _dist = 20
20 page.drawImage(
21 graphic_CSweek,
22 _dist,
23 yPos,
24 pgDim['w']-2*_dist,
25 (pgDim['w']-2*_dist)/3
26 )
27 yPos += 350
Footer
1def putFooter():
2 global page
3
4 # page.drawImage(logo, pgDim['w']-200, pgDim['h']-70, 125, 38) # Removed -- elections
5
6 # Set color
7 page.setFillColor(rlabColors.gray)
8
9 # Draw icons
10 page.setFont("FontAwesomeS",12)
11 page.drawCentredString(pgDim['w']/2 - 100, pgDim['h']-40,'') # Link
12 page.setFont("FontAwesomeB",12)
13 page.drawCentredString(pgDim['w']/2 - 110, pgDim['h']-25,'') # Facebook
14
15 # Draw Text
16 page.setFont("Montserrat",12)
17 page.drawCentredString(pgDim['w']/2, pgDim['h']-60, "Mais informações:") # xpos = 150
18 _site = 'novoportal.itabira.mg.gov.br/'
19 _fb = 'facebook.com/prefeituraitabira'
20
21 space = 100
22 pdfDrawLink('http://{}'.format(_site), pgDim['w']/2-space, pgDim['h']-50, 2*space, 10)
23 pdfDrawLink('https://{}'.format(_fb), pgDim['w']/2-space, pgDim['h']-35, 2*space, 10)
24
25 page.drawCentredString(pgDim['w']/2, pgDim['h']-40,_site)
26 page.drawCentredString(pgDim['w']/2, pgDim['h']-25,_fb)
Save
1def save():
2 global page
3
4 page.save()
Crescimento síndrome respiratória não especificada
1def putSecSeven():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 "CRESCIMENTO DE CASOS DE SÍNDROME RESPIRATÓRIA")
11 yPos += 30
12 page.drawCentredString(
13 pgDim['w']/2,
14 yPos,
15 "NÃO ESPECIFICADA")
16 yPos += 30
17 # Draw graphic disease
18 _dist = 20
19 page.drawImage(
20 graphic_Stimeline,
21 _dist,
22 yPos,
23 pgDim['w']-2*_dist,
24 (pgDim['w']-2*_dist)/3
25 )
26 yPos += 350
Crescimento de casos confirmados
1def putSecEight():
2 global page, yPos
3
4 # Titles
5 page.setFont("Montserrat",24)
6 page.setFillColor(rlabColors.gray)
7 page.drawCentredString(
8 pgDim['w']/2,
9 yPos,
10 "CRESCIMENTO DE CASOS CONFIRMADOS")
11 yPos += 30
12
13 # Draw graphic disease
14 _dist = 20
15 page.drawImage(
16 graphic_Ctimeline,
17 _dist,
18 yPos,
19 pgDim['w']-2*_dist,
20 (pgDim['w']-2*_dist)/3
21 )
22 yPos += 350
Generating the pdfs
Get internal pdf
1def getInternal(fileName):
2 pdf_Start(fileName)
3 setTitle('Boletim Interno')
4 putHeader('HeadOrange','Head2Orange')
5 putEmphasis()
6 putSecOne()
7 putSecTwo()
8 putSecThree()
9 putSecFour()
10 putSecFive()
11 putSecSix()
12 putSecSeven()
13 putSecEight()
14 putFooter()
15 save()
1# Internal PDF
2fileName = 'pdfs/Boletim-Interno_{}-{}-{}.pdf'.format(DAY,MONTH_NAME,YEAR)
3pgDim = {'w':792,'h':5450}
4
5getInternal(fileName)
Get external pdf
1def getExternal(fileName):
2 pdf_Start(fileName)
3 setTitle('Boletim Externo')
4 putHeader('BlueFB','Head2Blue')
5 putEmphasis()
6 putSecTwo()
7 putFooter()
8 save()
1# External PDF
2fileName = 'pdfs/Boletim-Externo_{}-{}-{}.pdf'.format(DAY,MONTH_NAME,YEAR)
3pgDim = {'w':792,'h':1150}
4
5getExternal(fileName)