add certs

This commit is contained in:
2025-09-01 16:34:02 -03:00
parent 5bd846338b
commit cbf8cada3d
16 changed files with 655 additions and 1108 deletions

View File

@@ -5,10 +5,12 @@ import boto3
if TYPE_CHECKING:
from mypy_boto3_dynamodb.client import DynamoDBClient
from mypy_boto3_s3 import S3Client
from mypy_boto3_sesv2 import SESV2Client
else:
DynamoDBClient = object
SESV2Client = object
S3Client = object
def get_dynamodb_client() -> DynamoDBClient:
@@ -18,5 +20,6 @@ def get_dynamodb_client() -> DynamoDBClient:
return boto3.client('dynamodb', endpoint_url='http://127.0.0.1:8000')
s3_client: S3Client = boto3.client('s3')
dynamodb_client: DynamoDBClient = get_dynamodb_client()
sesv2_client: SESV2Client = boto3.client('sesv2')

View File

@@ -1,53 +0,0 @@
import base64
import io
import locale
from datetime import date
from uuid import uuid4
import qrcode
from jinja2 import Template
from PIL import Image
from weasyprint import HTML
locale.setlocale(locale.LC_TIME, 'pt_BR')
today = date.today()
with open('nr10_complementar_sep.html', encoding='utf-8') as f:
html = f.read()
def cpf_fmt(s: str) -> str:
"""Returns a string as a Brazilian CPF number."""
return '{}.{}.{}-{}'.format(s[:3], s[3:6], s[6:9], s[9:])
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=3,
)
qr.add_data('https://eduseg.com.br')
qr.make(fit=True)
img = qr.make_image(fill_color='black', back_color='white')
img = img.resize((120, 120), Image.NEAREST)
buffer = io.BytesIO()
img.save(buffer, format='PNG')
img_str = base64.b64encode(buffer.getvalue()).decode('utf-8')
qrcode_base64 = f'data:image/png;base64,{img_str}'
template = Template(html)
html_rendered = template.render(
id=uuid4(),
name='Sérgio Rafael de Siqueira',
cpf=cpf_fmt('07879819908'),
progress=91.99,
course='NR-10 Complementar (SEP)',
today=today.strftime('%-d de %B de %Y'),
started_date=today.strftime('%d/%m/%Y'),
finished_date=today.strftime('%d/%m/%Y'),
qrcode=qrcode_base64,
)
HTML(string=html_rendered, base_url='').write_pdf('cert.pdf')

View File

@@ -1,28 +0,0 @@
[project]
name = "certs"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"jinja2>=3.1.6",
"layercake",
"qrcode>=8.2",
]
[tool.uv.sources]
layercake = { path = "../layercake" }
[tool.ruff]
target-version = "py311"
src = ["app"]
[tool.ruff.format]
quote-style = "single"
[tool.ruff.lint]
select = ["E", "F", "I"]
[dependency-groups]
dev = [
"ruff>=0.11.9",
]

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,10 @@ ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore
EMAIL_SENDER = ('EDUSEG®', 'noreply@eduseg.com.br')
PAPERFORGE_API = 'https://paperforge.saladeaula.digital'
# Post-migration: Remove the following lines
if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):

View File

@@ -1,15 +1,18 @@
import json
from datetime import datetime
import requests
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_classes import (
EventBridgeEvent,
event_source,
)
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from layercake.dateutils import fromisoformat, now
from layercake.dynamodb import DynamoDBPersistenceLayer, SortKey, TransactKey
from boto3clients import dynamodb_client
from config import (
ENROLLMENT_TABLE,
)
from boto3clients import dynamodb_client, s3_client
from config import BUCKET_NAME, ENROLLMENT_TABLE, PAPERFORGE_API
logger = Logger(__name__)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@@ -19,11 +22,87 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@logger.inject_lambda_context
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image']
course_metadata = enrollment_layer.collection.get_item(
KeyPair(
pk=new_image['id'],
sk='METADATA#COURSE',
),
now_ = now()
course = enrollment_layer.collection.get_items(
TransactKey(new_image['id'])
+ SortKey('METADATA#COURSE', path_spec='cert', rename_key='cert')
+ SortKey('STARTED', path_spec='started_at', rename_key='started_at')
+ SortKey('COMPLETED', path_spec='completed_at', rename_key='completed_at'),
flatten_top=False,
)
if 'cert' not in course:
# There is no certificate to issue from metadata
return False
started_at: datetime = fromisoformat(course['started_at']) # type: ignore
completed_at: datetime = fromisoformat(course['completed_at']) # type: ignore
json_data = json.dumps(
{
'template_s3_uri': course['cert']['s3_uri'],
'template_vars': {
'name': new_image['user']['name'],
'cpf': _cpffmt(new_image['user']['cpf']),
'score': new_image['score'],
'started_at': started_at.strftime('%d/%m/%Y'),
'completed_at': completed_at.strftime('%d/%m/%Y'),
'today': _datefmt(now_),
},
},
)
# Send template URI and data to Paperforge API to generate a PDF
r = requests.post(PAPERFORGE_API, data=json_data)
r.raise_for_status()
object_key = 'issuedcerts/{completed_at}_{id}.pdf'.format(
completed_at=completed_at.strftime('%Y-%m-%d'),
id=new_image['id'],
)
s3_uri = f's3://{BUCKET_NAME}/{object_key}'
try:
s3_client.put_object(
Bucket=BUCKET_NAME,
Key=object_key,
Body=r.content,
ContentType='application/pdf',
)
except Exception as exc:
logger.exception(exc)
raise
enrollment_layer.put_item(
item={
'id': new_image['id'],
'sk': 'ISSUED_CERT',
's3_uri': s3_uri,
'created_at': now_,
}
)
logger.info(f'PDF uploaded successfully to {s3_uri}')
return True
def _cpffmt(s: str) -> str:
return '{}.{}.{}-{}'.format(s[:3], s[3:6], s[6:9], s[9:])
def _datefmt(dt: datetime) -> str:
months = [
'Janeiro',
'Fevereiro',
'Março',
'Abril',
'Maio',
'Junho',
'Julho',
'Agosto',
'Setembro',
'Outubro',
'Novembro',
'Dezembro',
]
return f'{dt.day:02d} de {months[dt.month - 1]} de {dt.year}'

View File

@@ -50,9 +50,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'sk': 'METADATA#COURSE',
'created_at': now_,
'access_period': int(course['access_period']),
'cert': {
'exp_interval': int(course['cert']['exp_interval']),
},
'cert': course.get('cert', None),
}
)
except Exception as exc:

View File

@@ -0,0 +1,500 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>CIPA Grau de Risco 1</title>
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
<style>
html,
body,
div,
h1,
h2,
ul,
p,
a {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
@page {
size: A4 landscape;
margin: 0;
}
html {
font-family: Arial;
font-size: 13pt;
line-height: 1.4;
}
section {
width: 29.7cm;
height: 21cm;
break-after: page;
box-sizing: border-box;
}
strong {
font-weight: bold;
}
#cover {
background-color: #a7e400;
justify-content: center;
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
}
#cover .content {
background: #fff;
border-radius: 10px;
height: 100%;
padding: 2rem 4rem;
justify-content: center;
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
}
#cover .header {
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
}
#cover .header > div {
display: flex;
align-items: center;
}
#cover h1 {
font-weight: bolder;
font-size: 24pt;
}
#cover .signatures {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
#cover .copy {
text-align: center;
}
.sign1 {
width: 250px;
text-align: center;
border-top: #000 solid 1px;
}
.sign2 {
position: relative;
}
.sign2 img {
position: absolute;
top: -55px;
left: -55px;
width: 300px;
}
#back {
background-color: white;
display: flex;
flex-direction: row;
gap: 0.5rem;
padding: 5rem;
}
#back img.trainer {
width: 250px;
padding-bottom: 5px;
border-bottom: 1px solid #000;
}
#back h1,
#back h2 {
font-weight: bold;
font-size: 16pt;
}
#back ul {
padding-left: 1rem;
}
.space-y-0\.5 > :not(:last-child) {
margin-bottom: 0.125rem;
}
.space-y-2\.5 > :not(:last-child) {
margin-bottom: 0.625rem;
}
.space-y-5 > :not(:last-child) {
margin-bottom: 1.5rem;
}
</style>
</head>
<body>
<section id="cover">
<div class="content">
<div class="header">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1072.73 329.6"
style="width: 14rem"
>
<g>
<g>
<path
fill="#8cd366"
d="M152.18,217.62l-68.61-30.91c-1.88-1.2-4.28-1.2-6.16,0l-68.61,30.91c-3.81,2.43-8.8-.3-8.8-4.82V8.98C0,5.82,2.56,3.26,5.72,3.26h149.54c3.16,0,5.72,2.56,5.72,5.72v203.81c0,4.52-5,7.26-8.8,4.82Z"
></path>
<path
fill="#2e3524"
d="M93.97,74.01H26.61v20.16h67.36v-20.16Z"
></path>
<path
fill="#2e3524"
d="M107.44,111.16H26.61v26.8h80.83v-26.8Z"
></path>
<path
fill="#2e3524"
d="M107.44,30.27H26.61v26.8h80.83v-26.8Z"
></path>
<path
fill="#2e3524"
d="M134.38,131.23c0-3.72-3.02-6.73-6.73-6.73s-6.73,3.02-6.73,6.73,3.02,6.73,6.73,6.73,6.73-3.02,6.73-6.73Z"
></path>
</g>
<g>
<path
fill="#2E3524"
d="M244.7,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
></path>
<path
fill="#2E3524"
d="M362.72,3.24h57.79c10.71,0,20.47,2.35,29.29,7.06,8.83,4.7,15.79,11.18,20.87,19.39,5.08,8.21,7.63,17.45,7.63,27.67v214.88c0,10.22-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.83,4.7-18.73,7.08-29.7,7.08h-57.79V3.24ZM427.55,283.88c1.74-1.87,2.6-4.18,2.6-6.86V52.56c-.26-2.69-1.34-4.97-3.22-6.86-1.88-1.87-4.15-2.83-6.82-2.83h-14v243.85h14.41c2.93,0,5.27-.94,7.01-2.83h.02Z"
></path>
<path
fill="#2E3524"
d="M531.5,322.49c-8.71-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67V3.24h48.15v279.41c0,2.69.93,4.99,2.82,6.86,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.86V3.24h48.16v272.21c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.7-18.73,7.08-29.7,7.08s-20.8-2.35-29.5-7.08l.05-.02Z"
></path>
<path
fill="#2E3524"
d="M672.79,322.49c-8.7-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67v-78.75h48.16v85.95c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-77.88c0-5.66-2.22-10.3-6.63-13.94-4.41-3.62-11.57-8.02-21.47-13.13-8.3-4.3-15.05-8.14-20.27-11.52-5.22-3.36-9.71-7.87-13.45-13.54-3.75-5.66-5.63-12.24-5.63-19.8V54.12c0-10.22,2.53-19.44,7.63-27.67,5.08-8.21,11.97-14.66,20.68-19.39,8.68-4.7,18.53-7.06,29.5-7.06s20.87,2.35,29.69,7.06c8.83,4.7,15.72,11.18,20.68,19.39,4.96,8.21,7.42,17.45,7.42,27.67v71.09h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v69.79c0,6.19,2.34,11.26,7.04,15.14,4.67,3.91,12.24,8.83,22.68,14.74,8.04,4.32,14.57,8.09,19.68,11.3,5.08,3.24,9.37,7.46,12.83,12.72,3.48,5.26,5.22,11.26,5.22,17.98v86.83c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.71-18.72,7.08-29.7,7.08s-20.8-2.35-29.5-7.08Z"
></path>
<path
fill="#2E3524"
d="M784.56,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
></path>
<path
fill="#2E3524"
d="M920.63,322.49c-5.63-4.18-10.11-10.1-13.45-17.76-3.34-7.68-5.01-16.49-5.01-26.45V53.71c0-9.96,2.53-19.06,7.63-27.26,5.08-8.21,12.05-14.66,20.87-19.39,8.83-4.7,18.6-7.06,29.32-7.06s20.54,2.35,29.5,7.06c8.97,4.7,15.91,11.18,20.87,19.39,4.96,8.21,7.42,17.3,7.42,27.26v94.51h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v231.36c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-46.03h-11.64v-51.29h59.8v145.4h-48.16v-14.14c-2.96,5.4-6.82,9.48-11.64,12.31-4.82,2.83-10.83,4.25-18.06,4.25s-13.64-2.09-19.27-6.26l-.02-.02Z"
></path>
<path
fill="#2E3524"
d="M1053.27,25.05h-6.13l-.06-3.69h5.48c.83-.02,1.61-.15,2.33-.4.72-.27,1.3-.64,1.73-1.14.44-.51.65-1.14.65-1.87,0-.93-.16-1.67-.48-2.22-.3-.55-.83-.94-1.59-1.16-.74-.25-1.74-.37-3.01-.37h-3.78v20.42h-4.12V10.54h7.9c1.87,0,3.49.27,4.86.82,1.38.53,2.44,1.34,3.18,2.44.76,1.08,1.14,2.43,1.14,4.06,0,1.02-.24,1.93-.71,2.73-.47.8-1.17,1.49-2.1,2.07-.91.57-2.03,1.03-3.35,1.39-.06,0-.12.07-.2.2-.06.13-.11.2-.17.2-.32.19-.53.33-.63.43-.08.08-.16.12-.25.14-.08.02-.31.03-.68.03ZM1052.99,25.05l.6-2.81c2.95,0,4.97.64,6.05,1.93,1.08,1.27,1.62,2.89,1.62,4.86v1.53c0,.7.03,1.37.08,2.02.08.62.21,1.15.4,1.59v.45h-4.23c-.19-.49-.3-1.19-.34-2.1-.02-.91-.03-1.57-.03-1.99v-1.48c0-1.38-.31-2.39-.94-3.04s-1.69-.97-3.21-.97ZM1035.75,22.92c0,2.52.43,4.87,1.28,7.04.87,2.16,2.08,4.05,3.64,5.68,1.55,1.61,3.34,2.87,5.37,3.78,2.05.89,4.22,1.33,6.53,1.33s4.51-.44,6.53-1.33c2.03-.91,3.8-2.17,5.34-3.78,1.53-1.63,2.74-3.52,3.61-5.68.87-2.18,1.31-4.52,1.31-7.04s-.44-4.86-1.31-7.01c-.87-2.16-2.07-4.04-3.61-5.65-1.53-1.61-3.31-2.86-5.34-3.75-2.03-.91-4.2-1.36-6.53-1.36s-4.49.45-6.53,1.36c-2.03.89-3.81,2.14-5.37,3.75-1.55,1.61-2.77,3.49-3.64,5.65-.85,2.16-1.28,4.5-1.28,7.01ZM1032.4,22.92c0-3.01.52-5.8,1.56-8.38,1.04-2.57,2.49-4.82,4.34-6.73,1.86-1.93,4-3.43,6.42-4.49,2.44-1.08,5.06-1.62,7.84-1.62s5.39.54,7.81,1.62c2.44,1.06,4.58,2.56,6.42,4.49,1.86,1.91,3.31,4.16,4.35,6.73,1.06,2.57,1.59,5.37,1.59,8.38s-.53,5.8-1.59,8.38c-1.04,2.57-2.49,4.84-4.35,6.79-1.83,1.93-3.97,3.44-6.42,4.52-2.42,1.08-5.03,1.62-7.81,1.62s-5.39-.54-7.84-1.62c-2.42-1.08-4.56-2.58-6.42-4.52-1.85-1.95-3.3-4.21-4.34-6.79-1.04-2.57-1.56-5.37-1.56-8.38Z"
></path>
</g>
</g>
</svg>
</div>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 318.06 297.93"
style="width: 8rem"
>
<defs>
<style>
.cls-1 {
fill: #2e3524;
}
</style>
</defs>
<g id="Camada_1-2" data-name="Camada_1">
<g>
<path
class="cls-1"
d="M21.35,192.47l3.61,11.1,8.32-2.71-4.22-12.97,3.75-1.22,5.76,17.67-27.03,8.81-5.71-17.54,3.79-1.24,4.19,12.84,7.46-2.44-3.61-11.1,3.71-1.21h-.01Z"
/>
<path
class="cls-1"
d="M21.88,186.71c-3.21.5-5.91.15-8.1-1.05-2.17-1.19-3.46-3.05-3.85-5.55-.34-2.21.15-4.12,1.49-5.72l-10.7,1.65-.72-4.69,29.64-4.56.65,4.25-2.12.56c1.91,1.14,3.05,2.89,3.41,5.22.37,2.45-.3,4.57-2.04,6.38-1.74,1.81-4.3,2.99-7.67,3.5h.01ZM21.55,181.96c2.12-.32,3.71-.99,4.77-1.99s1.49-2.25,1.25-3.76c-.3-1.91-1.36-3.19-3.21-3.81l-8.91,1.37c-1.54,1.12-2.16,2.64-1.86,4.56.24,1.51,1.01,2.6,2.35,3.24,1.32.64,3.2.77,5.62.4h-.01Z"
/>
<path
class="cls-1"
d="M27.39,150.1c1.62,1.4,2.44,3.39,2.42,5.95-.01,2.29-.7,4.02-2.05,5.19-1.35,1.17-3.3,1.75-5.83,1.72l-13.68-.1.04-4.75,13.63.1c2.69.02,4.02-1.09,4.05-3.31.01-2.3-.8-3.86-2.45-4.69l-15.17-.1.04-4.75,21.13.15-.04,4.47-2.07.1h-.01Z"
/>
<path
class="cls-1"
d="M27.9,132.58c.19-1.17,0-2.2-.55-3.07-.55-.87-1.32-1.42-2.32-1.64l.71-4.41c1.29.26,2.44.86,3.44,1.8,1,.94,1.72,2.11,2.17,3.51.45,1.4.55,2.86.3,4.36-.47,2.91-1.79,5.07-3.96,6.48-2.16,1.4-4.91,1.84-8.23,1.3l-.47-.07c-3.17-.51-5.57-1.77-7.21-3.77-1.64-2-2.21-4.47-1.74-7.4.4-2.47,1.45-4.39,3.15-5.71,1.7-1.32,3.72-1.84,6.1-1.54l-.71,4.41c-1.2-.14-2.25.11-3.15.75-.89.64-1.44,1.55-1.62,2.74-.25,1.51.11,2.77,1.09,3.77.96,1,2.56,1.69,4.8,2.06l.75.12c2.26.36,4.01.22,5.26-.4,1.24-.64,1.99-1.72,2.24-3.27l-.02-.02Z"
/>
<path
class="cls-1"
d="M37.16,110.07c-.45.07-1.12.05-2.05-.09,1.04,1.92,1.22,3.92.55,6-.65,2.02-1.75,3.49-3.31,4.39s-3.2,1.09-4.89.54c-2.15-.69-3.54-2.01-4.17-3.97s-.47-4.42.47-7.38l.89-2.77-1.32-.42c-1.04-.34-1.96-.31-2.77.07-.81.39-1.41,1.16-1.79,2.36-.34,1.02-.35,1.95-.04,2.77.3.82.85,1.36,1.65,1.61l-1.45,4.51c-1.1-.35-2.01-1.05-2.74-2.09s-1.16-2.27-1.3-3.71c-.14-1.44.04-2.92.54-4.47.76-2.36,1.95-4.05,3.59-5.06,1.64-1.02,3.52-1.2,5.68-.55l9.07,2.91c1.81.59,3.34.79,4.57.62l.31.1-1.49,4.61v.02ZM32.31,113.99c.29-.89.34-1.8.16-2.72-.19-.92-.57-1.71-1.17-2.34l-3.79-1.21-.79,2.44c-.54,1.67-.65,3.02-.34,4.05s1,1.71,2.06,2.06c.87.27,1.65.21,2.35-.2.7-.41,1.2-1.1,1.51-2.06h0Z"
/>
<path
class="cls-1"
d="M40.76,93.24c.52-1.06.66-2.09.39-3.1-.26-1-.85-1.76-1.74-2.26l2-4c1.15.64,2.06,1.55,2.75,2.75.67,1.2,1.01,2.52,1.02,4,0,1.47-.32,2.89-1.01,4.25-1.31,2.65-3.22,4.31-5.7,5.01-2.49.7-5.24.29-8.26-1.21l-.44-.21c-2.87-1.44-4.8-3.35-5.76-5.75s-.79-4.92.54-7.58c1.12-2.25,2.69-3.75,4.71-4.51,2.01-.76,4.11-.65,6.27.35l-2,4c-1.11-.49-2.19-.56-3.22-.22-1.04.34-1.84,1.05-2.37,2.12-.69,1.37-.71,2.69-.09,3.92s1.95,2.37,3.97,3.4l.67.34c2.05,1.02,3.76,1.41,5.14,1.17,1.37-.24,2.41-1.05,3.11-2.45h.01ZM44.4,93.61l.85.66c1.19-1.59,2.62-1.96,4.32-1.11,1.22.61,1.92,1.59,2.11,2.94.19,1.35-.16,2.91-1.07,4.72l-2.45-1.07c.39-.77.54-1.42.47-1.97-.06-.55-.35-.95-.84-1.19-.52-.26-.99-.24-1.4.07-.41.31-.87.92-1.4,1.82l-2.15-1.77,1.54-3.1h.01Z"
/>
<path
class="cls-1"
d="M34.01,54.85c1.17.82,1.91,1.85,2.21,3.07.3,1.22.09,2.35-.64,3.36-.29.41-.59.74-.9.97-.3.24-.81.52-1.52.86s-1.19.59-1.41.77c-.22.19-.44.41-.64.69-.29.4-.37.85-.26,1.35.1.5.4.91.89,1.26l-1.81,2.29c-1.17-.82-1.91-1.86-2.24-3.1-.32-1.24-.12-2.36.59-3.37.42-.61,1.19-1.17,2.26-1.69,1.09-.51,1.82-1.05,2.21-1.61.29-.4.37-.86.27-1.35-.1-.5-.4-.92-.9-1.29l1.87-2.24h.01ZM56.32,74.49c-.45-.06-1.1-.29-1.92-.69.41,2.14,0,4.11-1.27,5.9-1.22,1.74-2.71,2.8-4.47,3.2s-3.37.09-4.82-.95c-1.84-1.3-2.77-2.97-2.79-5.04-.02-2.06.87-4.36,2.66-6.91l1.67-2.37-1.14-.8c-.89-.64-1.79-.89-2.67-.76-.89.12-1.7.69-2.41,1.71-.62.89-.91,1.76-.87,2.64.04.87.4,1.55,1.09,2.04l-2.74,3.87c-.95-.66-1.61-1.61-1.99-2.81-.37-1.21-.42-2.52-.12-3.92.29-1.41.91-2.77,1.85-4.11,1.42-2.02,3.07-3.27,4.94-3.76,1.86-.49,3.72-.09,5.58,1.17l7.78,5.5c1.55,1.1,2.95,1.75,4.19,1.97l.27.19-2.79,3.96-.02-.02ZM50.53,76.78c.54-.76.86-1.61.97-2.55.1-.94-.04-1.8-.42-2.59l-3.26-2.3-1.47,2.09c-1.01,1.44-1.52,2.69-1.54,3.77,0,1.07.44,1.94,1.35,2.59.75.52,1.51.7,2.3.51.79-.19,1.47-.69,2.06-1.51h.01Z"
/>
<path
class="cls-1"
d="M52.8,61.26c-1.49-1.45-2.52-3.05-3.14-4.79-.61-1.75-.72-3.46-.32-5.15.4-1.69,1.27-3.21,2.62-4.6,1.99-2.04,4.26-3.05,6.82-3.04,2.55.01,4.97,1.02,7.23,3.05l.81.76c1.49,1.46,2.54,3.05,3.15,4.76.61,1.72.72,3.42.32,5.12s-1.29,3.24-2.65,4.64c-2.09,2.14-4.46,3.15-7.13,3.04s-5.19-1.31-7.55-3.62l-.16-.16h-.01ZM56.41,58.16c1.55,1.52,3.09,2.39,4.6,2.6,1.51.21,2.82-.25,3.94-1.39,1.11-1.15,1.54-2.47,1.27-3.99-.27-1.51-1.26-3.11-2.99-4.8-1.52-1.49-3.06-2.35-4.6-2.56-1.54-.21-2.85.24-3.95,1.36-1.09,1.11-1.5,2.41-1.26,3.92s1.24,3.12,2.99,4.84h0Z"
/>
<path
class="cls-1"
d="M77.44,38.38c-1.76-2.8-2.52-5.42-2.27-7.86.25-2.45,1.46-4.35,3.64-5.72,2-1.25,4.01-1.55,6.06-.89l-.89-1.91,3.57-2.25,15.57,24.75-4.01,2.52-5.29-8.4c-.39,1.96-1.52,3.54-3.4,4.72-2.14,1.35-4.37,1.59-6.71.72s-4.44-2.76-6.28-5.7h.01ZM81.68,36.2c1.15,1.82,2.41,3.04,3.8,3.62,1.37.59,2.7.47,3.99-.32,1.59-1,2.34-2.41,2.26-4.26l-5.11-8.13c-1.59-.81-3.16-.72-4.74.25-1.27.8-1.96,1.95-2.05,3.44s.54,3.29,1.86,5.4h-.01Z"
/>
<path
class="cls-1"
d="M113.39,28.7c-.64,2.06-2.12,3.59-4.49,4.6-2.11.9-3.96.97-5.57.21-1.61-.76-2.92-2.31-3.92-4.65l-5.4-12.57,4.36-1.87,5.37,12.52c1.06,2.46,2.61,3.26,4.66,2.37,2.11-.91,3.22-2.29,3.31-4.12l-5.98-13.93,4.36-1.87,8.33,19.42-4.11,1.76-.92-1.86Z"
/>
<path
class="cls-1"
d="M131.14,25.68c-2.91.74-5.51.42-7.78-.95-2.27-1.37-3.8-3.59-4.59-6.63l-.15-.56c-.52-2.05-.59-3.97-.21-5.77.37-1.8,1.17-3.34,2.36-4.6,1.2-1.26,2.67-2.11,4.42-2.56,2.79-.71,5.16-.36,7.13,1.02,1.97,1.4,3.37,3.71,4.2,6.97l.47,1.85-13.38,3.4c.58,1.65,1.47,2.85,2.71,3.59,1.24.72,2.61.91,4.11.52,2.11-.54,3.6-1.82,4.5-3.86l3.07,1.74c-.51,1.44-1.36,2.66-2.56,3.69-1.2,1.02-2.65,1.75-4.35,2.19l.04-.02ZM126.13,8.29c-1.26.32-2.16,1.02-2.72,2.1-.55,1.07-.74,2.44-.55,4.06l8.76-2.24-.09-.34c-.5-1.51-1.2-2.57-2.12-3.17-.91-.6-2.01-.75-3.29-.42h.01Z"
/>
<path
class="cls-1"
d="M150.25,10.96c-.06-3.27.65-5.91,2.15-7.9,1.5-1.99,3.52-3.01,6.08-3.06,2.41-.05,4.32.75,5.73,2.4l.17-2.14,4.27-.09.41,20.48c.05,2.77-.76,4.97-2.45,6.61-1.69,1.64-4,2.49-6.92,2.54-1.55.04-3.07-.26-4.56-.87-1.49-.61-2.62-1.44-3.41-2.46l2.19-2.9c1.49,1.7,3.3,2.54,5.44,2.49,1.57-.04,2.81-.49,3.7-1.35.89-.87,1.32-2.14,1.29-3.79l-.02-1.42c-1.34,1.55-3.15,2.35-5.42,2.4-2.47.05-4.51-.89-6.12-2.82-1.61-1.94-2.45-4.64-2.51-8.11h-.01ZM154.97,11.27c.05,2.12.51,3.79,1.4,4.97.89,1.2,2.1,1.79,3.64,1.75,1.91-.04,3.31-.89,4.21-2.55l-.19-9.3c-.92-1.59-2.35-2.35-4.26-2.31-1.56.04-2.76.67-3.6,1.92-.84,1.25-1.24,3.09-1.19,5.5h-.01Z"
/>
<path
class="cls-1"
d="M185.4,23.29c-.15-.42-.24-1.1-.27-2.02-1.71,1.35-3.65,1.87-5.81,1.59-2.1-.29-3.74-1.12-4.9-2.51-1.16-1.39-1.62-2.96-1.37-4.72.31-2.22,1.37-3.82,3.19-4.79,1.82-.96,4.27-1.24,7.36-.81l2.89.4.19-1.37c.15-1.09-.04-1.99-.55-2.72s-1.4-1.19-2.64-1.36c-1.07-.15-1.99,0-2.74.44-.75.44-1.2,1.07-1.31,1.9l-4.7-.65c.15-1.15.69-2.17,1.59-3.06s2.04-1.54,3.42-1.92c1.39-.39,2.89-.47,4.5-.25,2.45.34,4.32,1.22,5.61,2.65,1.29,1.42,1.8,3.26,1.52,5.5l-1.3,9.43c-.26,1.89-.2,3.42.17,4.61l-.05.32-4.8-.66v.02ZM180.71,19.19c.92.12,1.84.02,2.71-.31.89-.34,1.57-.86,2.1-1.56l.55-3.95-2.54-.35c-1.74-.24-3.09-.11-4.05.36-.96.49-1.51,1.29-1.67,2.39-.12.9.07,1.66.6,2.29s1.3,1,2.3,1.14Z"
/>
<path
class="cls-1"
d="M208.47,11.57c-.57-.26-1.19-.49-1.82-.65-2.1-.56-3.74-.15-4.9,1.27l-3.76,13.94-4.59-1.24,5.51-20.39,4.37,1.17-.5,2.31c1.59-1.47,3.36-1.95,5.32-1.41.65.17,1.17.41,1.55.7l-1.17,4.3h-.01Z"
/>
<path
class="cls-1"
d="M217.34,32.92c-.04-.45.04-1.12.24-2.04-1.99.89-4,.92-6.02.1-1.96-.8-3.35-2.01-4.12-3.64-.77-1.62-.85-3.26-.17-4.92.85-2.09,2.27-3.37,4.27-3.86,2-.49,4.45-.14,7.32,1.02l2.7,1.1.52-1.29c.41-1.01.46-1.94.14-2.77-.32-.84-1.06-1.5-2.21-1.96-1-.4-1.92-.49-2.76-.25s-1.42.75-1.74,1.52l-4.4-1.79c.44-1.07,1.2-1.94,2.29-2.57,1.09-.65,2.35-.99,3.8-1.01,1.44-.04,2.91.26,4.42.87,2.29.94,3.89,2.25,4.77,3.96.9,1.71.94,3.61.12,5.71l-3.59,8.82c-.71,1.76-1.04,3.26-.97,4.51l-.12.31-4.49-1.82h0ZM213.79,27.8c.87.35,1.77.47,2.71.36s1.74-.45,2.42-1l1.5-3.69-2.37-.96c-1.62-.66-2.96-.87-4.01-.64-1.05.24-1.79.87-2.21,1.91-.35.84-.34,1.62.02,2.36.36.72,1.01,1.29,1.95,1.66h-.01Z"
/>
<path
class="cls-1"
d="M239.28,20.36l-1.14,2.16c2.31-.81,4.55-.56,6.72.75,3.77,2.27,4.39,5.58,1.85,9.93l-7.21,11.96-4.06-2.45,7.06-11.72c.69-1.15.96-2.15.79-3s-.81-1.61-1.94-2.3c-1.64-.99-3.3-.99-5,.01l-7.76,12.87-4.06-2.45,10.91-18.09,3.82,2.31h.01Z"
/>
<path
class="cls-1"
d="M263.61,30.79l-3.19,4.02,2.92,2.31-2.19,2.76-2.92-2.31-7.32,9.25c-.5.64-.74,1.19-.71,1.66s.36.97,1,1.49c.42.34.9.64,1.42.87l-2.27,2.87c-1.04-.44-1.94-.96-2.72-1.59-2.86-2.26-3.04-4.97-.54-8.12l7.43-9.38-2.72-2.16,2.19-2.76,2.72,2.16,3.19-4.02,3.72,2.95h-.01Z"
/>
<path
class="cls-1"
d="M258.67,62.34c-2.11-2.15-3.14-4.55-3.1-7.21.05-2.66,1.19-5.09,3.44-7.3l.41-.41c1.51-1.47,3.14-2.51,4.89-3.09,1.75-.57,3.47-.67,5.16-.26,1.69.4,3.17,1.25,4.44,2.54,2.01,2.05,2.91,4.29,2.7,6.68-.21,2.41-1.51,4.79-3.91,7.13l-1.36,1.34-9.67-9.85c-1.15,1.32-1.72,2.7-1.72,4.15s.52,2.71,1.61,3.82c1.52,1.55,3.39,2.2,5.6,1.95l.05,3.54c-1.5.27-2.99.16-4.47-.36-1.49-.52-2.85-1.41-4.07-2.66h.02ZM271.19,49.26c-.91-.92-1.97-1.36-3.19-1.29-1.21.06-2.47.59-3.79,1.57l6.33,6.45.25-.25c1.06-1.19,1.62-2.32,1.69-3.42s-.36-2.12-1.29-3.06h-.01Z"
/>
<path
class="cls-1"
d="M283.39,84.8c.72-.45,1.09-1.09,1.09-1.91s-.27-2.01-.84-3.56c-.56-1.55-.91-2.92-1.04-4.11-.27-2.61.5-4.5,2.35-5.65,1.55-.96,3.25-1.11,5.1-.46,1.85.66,3.4,1.99,4.65,4,1.34,2.14,1.89,4.21,1.64,6.18-.24,1.97-1.2,3.5-2.9,4.55l-2.51-4.02c.77-.49,1.24-1.17,1.4-2.06.15-.9-.06-1.81-.65-2.76-.55-.89-1.2-1.47-1.96-1.79-.75-.3-1.47-.24-2.16.19-.62.39-.94.94-.96,1.67-.01.74.29,2,.92,3.8.64,1.8.99,3.3,1.07,4.5.07,1.2-.07,2.22-.49,3.09-.4.86-1.09,1.59-2.05,2.19-1.61,1-3.34,1.15-5.17.44-1.84-.71-3.42-2.16-4.77-4.32-.91-1.46-1.46-2.94-1.65-4.42-.19-1.47,0-2.82.51-4.05s1.32-2.16,2.4-2.82l2.44,3.91c-.91.65-1.42,1.46-1.52,2.45-.1.99.19,2.02.87,3.11.66,1.06,1.36,1.74,2.11,2.05.75.31,1.45.25,2.1-.16h.02Z"
/>
<path
class="cls-1"
d="M289.39,105.55c-2.05-.64-3.59-2.14-4.6-4.49-.9-2.11-.96-3.96-.2-5.57.77-1.61,2.32-2.91,4.65-3.91l12.58-5.39,1.86,4.36-12.53,5.36c-2.46,1.06-3.26,2.61-2.39,4.65.91,2.12,2.28,3.22,4.11,3.31l13.94-5.97,1.86,4.36-19.42,8.32-1.76-4.11,1.85-.92h.04Z"
/>
<path
class="cls-1"
d="M293.58,126.44c.34-.3.94-.64,1.77-1.01-1.9-1.07-3.11-2.67-3.65-4.8-.52-2.06-.36-3.89.49-5.47.85-1.6,2.14-2.61,3.86-3.05,2.19-.55,4.06-.16,5.63,1.16,1.57,1.32,2.75,3.5,3.51,6.51l.71,2.82,1.35-.34c1.06-.27,1.84-.77,2.32-1.54s.57-1.74.27-2.95c-.26-1.05-.75-1.84-1.44-2.37s-1.44-.7-2.25-.5l-1.16-4.6c1.12-.29,2.27-.17,3.44.32s2.19,1.31,3.06,2.46c.87,1.15,1.51,2.5,1.91,4.07.6,2.4.49,4.46-.36,6.2-.85,1.72-2.35,2.89-4.52,3.47l-9.23,2.34c-1.84.46-3.25,1.1-4.21,1.89l-.32.07-1.19-4.7ZM295.62,120.55c.22.91.66,1.71,1.31,2.4s1.39,1.14,2.24,1.36l3.86-.97-.62-2.49c-.44-1.7-1.05-2.91-1.86-3.61-.81-.71-1.76-.92-2.85-.65-.89.22-1.51.7-1.89,1.41-.37.72-.44,1.57-.19,2.55Z"
/>
<path
class="cls-1"
d="M302.38,155.39c.85-.02,1.49-.39,1.91-1.09s.79-1.87,1.09-3.5c.3-1.62.69-2.97,1.19-4.07,1.09-2.4,2.71-3.62,4.89-3.67,1.82-.05,3.36.69,4.62,2.19,1.26,1.51,1.92,3.44,1.99,5.81.06,2.52-.51,4.57-1.72,6.16-1.21,1.59-2.81,2.4-4.8,2.45l-.11-4.75c.91-.02,1.66-.37,2.25-1.07.59-.69.87-1.6.84-2.71-.02-1.04-.29-1.89-.79-2.52-.5-.65-1.15-.96-1.96-.94-.72.02-1.29.34-1.67.96s-.76,1.87-1.12,3.74c-.36,1.87-.81,3.34-1.35,4.42-.54,1.07-1.2,1.87-1.97,2.41-.77.54-1.74.81-2.87.84-1.9.05-3.46-.7-4.67-2.25-1.21-1.55-1.86-3.6-1.92-6.15-.05-1.72.24-3.27.82-4.65s1.42-2.45,2.5-3.22c1.07-.79,2.24-1.19,3.51-1.22l.11,4.61c-1.11.09-1.97.54-2.55,1.34-.59.8-.86,1.84-.84,3.12.04,1.25.29,2.19.79,2.84.5.65,1.12.95,1.89.92h-.02Z"
/>
<path
class="cls-1"
d="M295.03,172.54c.37-2.99,1.61-5.29,3.72-6.9,2.11-1.61,4.74-2.24,7.86-1.84l.57.07c2.09.26,3.91.9,5.46,1.91s2.69,2.31,3.42,3.89c.74,1.57.99,3.26.76,5.05-.36,2.85-1.55,4.95-3.56,6.27-2.01,1.32-4.69,1.79-8.02,1.36l-1.9-.24,1.72-13.69c-1.75-.07-3.19.32-4.32,1.21-1.14.89-1.8,2.1-1.99,3.64-.27,2.16.37,4.02,1.95,5.6l-2.74,2.24c-1.15-1-1.97-2.24-2.49-3.72-.51-1.49-.66-3.1-.45-4.85h-.01ZM313.06,174.24c.16-1.29-.16-2.39-.96-3.3-.8-.91-2-1.57-3.59-2l-1.12,8.97.35.05c1.59.1,2.84-.17,3.74-.81.9-.64,1.42-1.6,1.6-2.91h-.01Z"
/>
<path
class="cls-1"
d="M304.31,185.74c3.15.91,5.45,2.37,6.91,4.4,1.45,2.02,1.82,4.26,1.11,6.71-.66,2.31-2,3.9-4,4.76l1.99.8-1.19,4.11-19.68-5.67c-2.66-.77-4.52-2.2-5.58-4.3-1.06-2.1-1.19-4.56-.37-7.37.42-1.49,1.16-2.85,2.19-4.1,1.02-1.24,2.15-2.09,3.36-2.54l2.11,2.95c-2.06.92-3.4,2.41-3.99,4.46-.44,1.51-.37,2.82.2,3.94.57,1.11,1.64,1.9,3.24,2.35l1.37.4c-1.09-1.74-1.31-3.7-.67-5.88.69-2.37,2.19-4.05,4.51-5.01,2.32-.96,5.15-.96,8.5,0ZM302.61,190.17c-2.04-.59-3.76-.64-5.17-.14-1.41.5-2.32,1.47-2.75,2.95-.52,1.84-.14,3.42,1.19,4.77l8.93,2.57c1.79-.42,2.95-1.55,3.47-3.39.44-1.5.17-2.84-.77-4.01s-2.57-2.1-4.9-2.76Z"
/>
<path
class="cls-1"
d="M283.9,216.72c-.89-1.96-.79-4.1.31-6.42.97-2.07,2.32-3.35,4.04-3.85,1.71-.49,3.72-.2,6.01.89l12.38,5.83-2.02,4.29-12.33-5.81c-2.42-1.15-4.11-.71-5.06,1.3-.99,2.09-.9,3.85.25,5.27l13.72,6.47-2.02,4.29-19.1-9.01,1.91-4.05,1.92.77v.02Z"
/>
<path
class="cls-1"
d="M286.1,241.67c.42-.47.81-.99,1.17-1.54,1.17-1.82,1.26-3.51.26-5.05l-12.16-7.8,2.56-4,17.78,11.39-2.45,3.81-2.05-1.17c.92,1.95.84,3.79-.25,5.5-.36.57-.75.99-1.14,1.26l-3.74-2.42h0Z"
/>
<path
class="cls-1"
d="M263.04,243.68c.45.1,1.06.37,1.86.84-.25-2.16.32-4.1,1.72-5.77,1.36-1.62,2.92-2.57,4.72-2.84,1.79-.26,3.36.19,4.74,1.32,1.74,1.44,2.52,3.19,2.39,5.25-.15,2.06-1.21,4.29-3.2,6.67l-1.86,2.24,1.06.89c.84.7,1.71,1.02,2.61.97.9-.05,1.74-.55,2.54-1.51.69-.82,1.05-1.69,1.07-2.56.02-.87-.27-1.59-.92-2.11l3.04-3.65c.89.74,1.47,1.72,1.76,2.96.29,1.24.22,2.55-.17,3.92-.4,1.39-1.12,2.7-2.16,3.95-1.59,1.9-3.32,3.02-5.21,3.36-1.89.34-3.72-.2-5.47-1.61l-7.32-6.1c-1.46-1.21-2.8-1.97-4.01-2.3l-.25-.21,3.1-3.72h-.02ZM269.02,241.86c-.6.72-.99,1.55-1.16,2.47-.17.92-.11,1.8.21,2.61l3.06,2.55,1.64-1.96c1.12-1.35,1.74-2.56,1.82-3.64.1-1.07-.29-1.96-1.15-2.69-.7-.57-1.45-.81-2.25-.69-.8.12-1.52.57-2.17,1.35h0Z"
/>
<path
class="cls-1"
d="M268.33,268.44l-1.71-1.75c.06,2.45-.85,4.51-2.76,6.18-3.31,2.9-6.65,2.49-10.03-1.25l-9.2-10.5,3.57-3.12,9.02,10.3c.89,1.01,1.75,1.56,2.61,1.66s1.79-.27,2.77-1.15c1.44-1.26,1.94-2.85,1.5-4.77l-9.91-11.31,3.57-3.12,13.92,15.88-3.36,2.95Z"
/>
<path
class="cls-1"
d="M233.17,268.63l-.37-1.01c-1.84.74-3.26.31-4.27-1.3-.72-1.16-.82-2.36-.29-3.6.54-1.25,1.66-2.41,3.37-3.49l1.54,2.19c-.72.46-1.2.94-1.44,1.45-.22.51-.2.99.1,1.45.31.5.72.71,1.24.66.51-.05,1.22-.32,2.14-.82l.91,2.62-2.92,1.84h0ZM236.09,270.86c-1,.62-1.65,1.45-1.94,2.45-.3,1-.19,1.94.3,2.82l-3.79,2.39c-.66-1.14-.96-2.4-.92-3.77.05-1.37.45-2.69,1.2-3.95.76-1.26,1.79-2.3,3.07-3.11,2.5-1.57,5-2.01,7.47-1.31,2.49.7,4.62,2.47,6.42,5.32l.26.41c1.71,2.72,2.35,5.36,1.92,7.91-.42,2.55-1.9,4.62-4.41,6.2-2.12,1.34-4.25,1.81-6.37,1.41-2.12-.4-3.85-1.59-5.17-3.56l3.79-2.39c.69,1,1.57,1.61,2.64,1.86,1.06.25,2.11.05,3.12-.59,1.3-.82,2.01-1.92,2.12-3.31.11-1.39-.42-3.04-1.62-4.96l-.41-.65c-1.22-1.94-2.47-3.16-3.77-3.67-1.3-.51-2.61-.35-3.94.47l.02.02Z"
/>
<path
class="cls-1"
d="M213.07,278.96c.35.29.79.81,1.3,1.59.75-2.05,2.11-3.52,4.11-4.41,1.94-.86,3.77-1.01,5.49-.44,1.71.56,2.94,1.66,3.66,3.29.91,2.06.85,3.97-.2,5.76-1.04,1.77-2.99,3.3-5.83,4.56l-2.66,1.17.56,1.26c.45,1,1.07,1.67,1.9,2.02.82.35,1.81.27,2.95-.22.99-.44,1.69-1.04,2.1-1.81s.45-1.54.11-2.3l4.34-1.92c.47,1.06.56,2.2.26,3.44-.29,1.24-.92,2.37-1.91,3.44-.97,1.06-2.21,1.91-3.7,2.57-2.26,1-4.31,1.22-6.16.69-1.85-.54-3.25-1.84-4.2-3.87l-3.86-8.71c-.77-1.74-1.62-3.01-2.57-3.84l-.14-.3,4.42-1.96h.04ZM219.21,279.98c-.86.37-1.57.95-2.14,1.7-.57.75-.89,1.56-.96,2.44l1.61,3.65,2.34-1.04c1.61-.71,2.69-1.52,3.25-2.45.56-.92.61-1.89.16-2.91-.36-.84-.94-1.37-1.71-1.62-.77-.25-1.62-.16-2.55.25h0Z"
/>
<path
class="cls-1"
d="M203.98,287.44c-.85.26-1.57.22-2.19-.11s-1.02-.86-1.25-1.59c-.22-.71-.17-1.36.15-1.97.31-.61.9-1.04,1.74-1.3.81-.25,1.52-.21,2.14.1s1.04.84,1.25,1.55c.22.72.17,1.39-.14,2.01s-.89,1.06-1.72,1.31h.02Z"
/>
<path
class="cls-1"
d="M181.38,144.05h-73.14v21.89h73.14v-21.89Z"
/>
<path
class="cls-1"
d="M196.01,184.39h-87.77v29.11h87.77v-29.11Z"
/>
<path
class="cls-1"
d="M196.01,96.56h-87.77v29.11h87.77v-29.11Z"
/>
<path
class="cls-1"
d="M217.96,198.87h-.01c-4.04,0-7.31,3.27-7.31,7.31h0c0,4.05,3.27,7.32,7.31,7.32h.01c4.04,0,7.31-3.27,7.31-7.31h0c0-4.05-3.27-7.32-7.31-7.32Z"
/>
</g>
</g>
</svg>
</div>
</div>
<p>Certificamos que</p>
<h1>{{ name }}</h1>
<p>
Portador(a) do CPF <strong>{{ cpf }}</strong>, concluiu o
curso de <strong>CIPA Grau de Risco 1</strong> com
aproveitamento de
<strong>{{ score }}%</strong>
</p>
<p>
Realizado entre
<strong>{{ started_at }}</strong> e
<strong>{{ completed_at }}</strong>
</p>
<p>
Data de emissão do certificado: <strong>{{ today }}</strong>
</p>
<div class="signatures">
<div class="sign1">{{ name }}</div>
<div>
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/eduseg_digital_tag.png"
style="width: 200px"
/>
</div>
<div class="sign2">
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/tiago_sign.png"
alt="Tiago Maciel dos Santos"
/>
<p><strong>Tiago Maciel do Santos</strong></p>
<p>Responsável legal</p>
<p>EDUSEG LTDA</p>
<p>CNPJ 15.608.435/0001-90</p>
</div>
</div>
<div class="copy">
&copy; 2025 EDUSEG&reg; Todos os direitos reservados. CNPJ
15.608.435/0001-90
</div>
</div>
</section>
<section id="back">
<div class="space-y-2.5">
<h1>Conteúdo programático ministrado</h1>
<ul>
<li>
Estudo do ambiente, das condições de trabalho, bem como
dos riscos originados do processo produtivo
</li>
<li>
Noções sobre acidentes e doenças relacionadas ao
trabalho decorrentes das condições de trabalho e da
exposição aos riscos existentes no estabelecimento e
suas medidas de prevenção
</li>
<li>
Metodologia de investigação e análise de acidentes e
doenças relacionadas ao trabalho
</li>
<li>
Princípios gerais de higiene do trabalho e de medidas de
prevenção dos riscos
</li>
<li>
Noções sobre as legislações trabalhista e previdenciária
relativas à segurança e saúde no trabalho
</li>
<li>
Noções sobre a inclusão de pessoas com deficiência e
reabilitados nos processos de trabalho
</li>
<li>
Violência, assédio, igualdade e diversidade no âmbito do
trabalho
</li>
<li>
Organização da CIPA e outros assuntos necessários ao
exercício das atribuições da Comissão
</li>
</ul>
</div>
<div class="space-y-5">
<dd class="space-y-0.5">
<h2>Treinamento ministrado</h2>
<p>CIPA Grau de Risco 1</p>
</dd>
<dd class="space-y-0.5">
<h2>Carga horária</h2>
<p>8 horas</p>
</dd>
<dd class="space-y-0.5">
<h2>Validade</h2>
<p>24 meses após conclusão</p>
</dd>
<dd class="space-y-0.5">
<h2>Instrutor e responsável técnico</h2>
<div>
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/francis_sign.png"
class="trainer"
/>
<p><strong>Francis Ricardo Baretta</strong></p>
<p>CPF 039.539.409-02</p>
<p>Eng. de Segurança no Trabalho Eng. Eletricista</p>
<p>CREA/SC 126693-0</p>
</div>
<img
src="https://s3.sa-east-1.amazonaws.com/saladeaula.digital/samples/francis_digital_tag.png"
style="width: 300px"
/>
</dd>
</div>
</section>
</body>
</html>

View File

@@ -4,11 +4,13 @@ version = "0.1.0"
description = ""
readme = ""
requires-python = ">=3.13"
dependencies = ["layercake"]
dependencies = [
"layercake",
]
[dependency-groups]
dev = [
"boto3-stubs[dynamodb,sesv2]>=1.40.15",
"boto3-stubs[dynamodb,s3,sesv2]>=1.40.15",
"jsonlines>=4.0.0",
"pytest>=8.3.4",
"pytest-cov>=6.0.0",

View File

@@ -2,6 +2,9 @@ AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Parameters:
BucketName:
Type: String
Default: saladeaula.digital
UserTable:
Type: String
Default: betaeducacao-prod-users_d2o3r5gmm4it7j
@@ -35,6 +38,7 @@ Globals:
ENROLLMENT_TABLE: !Ref EnrollmentTable
ORDER_TABLE: !Ref OrderTable
COURSE_TABLE: !Ref CourseTable
BUCKET_NAME: !Ref BucketName
Resources:
EventLog:
@@ -371,6 +375,8 @@ Resources:
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref EnrollmentTable
- S3WritePolicy:
BucketName: !Ref BucketName
Events:
DynamoDBEvent:
Type: EventBridgeRule

View File

@@ -19,6 +19,7 @@ def pytest_configure():
os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
@dataclass

View File

@@ -0,0 +1,25 @@
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer
import events.issue_cert as app
def test_issue_cert(
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
lambda_context: LambdaContext,
):
event = {
'detail': {
'new_image': {
'id': '1ee108ae-67d4-4545-bf6d-4e641cdaa4e0',
'user': {
'name': 'Jimi Hendrix',
'cpf': '74630003037',
},
'score': 79,
'status': 'COMPLETED',
}
}
}
assert app.lambda_handler(event, lambda_context) # type: ignore

View File

@@ -22,4 +22,9 @@
// Enrollment
{"id": "6437a282-6fe8-4e4d-9eb0-da1007238007", "sk": "0", "status": "IN_PROGRESS", "progress": 10}
{"id": "845fe390-e3c3-4514-97f8-c42de0566cf0", "sk": "0", "status": "COMPLETED", "progress": 100}
{"id": "14682b79-3df2-4351-9229-8b558af046a0", "sk": "METADATA#COURSE", "access_period": 360}
{"id": "14682b79-3df2-4351-9229-8b558af046a0", "sk": "METADATA#COURSE", "access_period": 360}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "0", "score": 100, "course": {"name": "CIPA Grau de Risco 1"}, "user": {"name": "Kurt Cobain"}}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "METADATA#COURSE", "cert": {"s3_uri": "s3://saladeaula.digital/certs/3c27ea9c-9464-46a1-9717-8c1441793186-cipa-grau-de-risco-1.html"}}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "STARTED", "started_at": "2025-08-24T01:44:42.703012-03:06"}
{"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "COMPLETED", "completed_at": "2025-08-31T21:59:10.842467-03:00"}

View File

@@ -132,6 +132,9 @@ wheels = [
dynamodb = [
{ name = "mypy-boto3-dynamodb" },
]
s3 = [
{ name = "mypy-boto3-s3" },
]
sesv2 = [
{ name = "mypy-boto3-sesv2" },
]
@@ -367,7 +370,7 @@ dependencies = [
[package.dev-dependencies]
dev = [
{ name = "boto3-stubs", extra = ["dynamodb", "sesv2"] },
{ name = "boto3-stubs", extra = ["dynamodb", "s3", "sesv2"] },
{ name = "jsonlines" },
{ name = "pytest" },
{ name = "pytest-cov" },
@@ -379,7 +382,7 @@ requires-dist = [{ name = "layercake", directory = "../layercake" }]
[package.metadata.requires-dev]
dev = [
{ name = "boto3-stubs", extras = ["dynamodb", "sesv2"], specifier = ">=1.40.15" },
{ name = "boto3-stubs", extras = ["dynamodb", "s3", "sesv2"], specifier = ">=1.40.15" },
{ name = "jsonlines", specifier = ">=4.0.0" },
{ name = "pytest", specifier = ">=8.3.4" },
{ name = "pytest-cov", specifier = ">=6.0.0" },
@@ -566,6 +569,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/b3/6f2e15a44e66a8cc98fd1032f3aba770f946ba361782a0a979a115fdf6e2/mypy_boto3_dynamodb-1.40.14-py3-none-any.whl", hash = "sha256:302cc169dde3b87a41924855dcfbae173247e18833dee80919f7cc690189f376", size = 57017, upload-time = "2025-08-20T19:27:20.701Z" },
]
[[package]]
name = "mypy-boto3-s3"
version = "1.40.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/06/d7/b2100702d2f200fdb3468e419c729790bd8543ee0af6f6d63d8dfdab4e28/mypy_boto3_s3-1.40.0.tar.gz", hash = "sha256:99a4a27f04d62fe0b31032f274f2e19889fa66424413617a9416873c48567f1d", size = 75924, upload-time = "2025-07-31T19:50:01.979Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/4f/4d32cd202d8c8c7e11e44dd288f66b8985e6ee4402b9a0891b7b94ff6cc6/mypy_boto3_s3-1.40.0-py3-none-any.whl", hash = "sha256:5736b7780d57a156312d8d136462c207671d0236b0355704b5754496bb712bc8", size = 82710, upload-time = "2025-07-31T19:49:59.713Z" },
]
[[package]]
name = "mypy-boto3-sesv2"
version = "1.40.0"