wip reenroll

This commit is contained in:
2025-09-11 21:53:08 -03:00
parent ac07ee0101
commit e51964bc8b
13 changed files with 106 additions and 567 deletions

View File

@@ -1,5 +1,4 @@
from dataclasses import asdict, dataclass from typing import NotRequired, Self, TypedDict
from typing import Self, TypedDict
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
@@ -8,8 +7,16 @@ from layercake.strutils import md5_hash
from schemas import Enrollment from schemas import Enrollment
Tenant = TypedDict('Tenant', {'id': str, 'name': str}) Tenant = TypedDict('Tenant', {'id': str, 'name': str})
Author = TypedDict('Author', {'id': str, 'name': str}) CreatedBy = TypedDict('CreatedBy', {'id': str, 'name': str})
DeduplicationWindow = TypedDict('DeduplicationWindow', {'offset_days': int}) DeduplicationWindow = TypedDict('DeduplicationWindow', {'offset_days': int})
Subscription = TypedDict(
'Subscription',
{
'org_id': str,
'billing_day': int,
'billing_period': NotRequired[str],
},
)
class LinkedEntity(str): class LinkedEntity(str):
@@ -23,32 +30,16 @@ class LinkedEntity(str):
self.type = type self.type = type
@dataclass(frozen=True)
class Slot:
id: str
sk: str
@property
def order_id(self) -> LinkedEntity:
idx, _ = self.sk.split('#')
return LinkedEntity(idx, 'ORDER')
class DeduplicationConflictError(Exception): class DeduplicationConflictError(Exception):
def __init__(self, *args): def __init__(self, *args):
super().__init__('Enrollment already exists') super().__init__('Enrollment already exists')
class SlotDoesNotExistError(Exception):
def __init__(self, *args):
super().__init__('Slot does not exist')
def enroll( def enroll(
enrollment: Enrollment, enrollment: Enrollment,
*, *,
slot: Slot | None = None, created_by: CreatedBy | None = None,
author: Author | None = None, subscription: Subscription | None = None,
linked_entities: frozenset[LinkedEntity] = frozenset(), linked_entities: frozenset[LinkedEntity] = frozenset(),
deduplication_window: DeduplicationWindow | None = None, deduplication_window: DeduplicationWindow | None = None,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
@@ -60,9 +51,6 @@ def enroll(
lock_hash = md5_hash('%s%s' % (user.id, course.id)) lock_hash = md5_hash('%s%s' % (user.id, course.id))
with persistence_layer.transact_writer() as transact: with persistence_layer.transact_writer() as transact:
if slot:
linked_entities = frozenset({slot.order_id}) | linked_entities
transact.put( transact.put(
item={ item={
'sk': '0', 'sk': '0',
@@ -90,46 +78,33 @@ def enroll(
} }
) )
if slot: if created_by:
transact.put(
item={
'id': enrollment.id,
# Post-migration: uncomment the following line
# 'sk': 'METADATA#SOURCE_SLOT',
'sk': 'parent_vacancy',
'vacancy': asdict(slot),
'created_at': now_,
}
)
transact.delete(
key=KeyPair(slot.id, slot.sk),
cond_expr='attribute_exists(sk)',
exc_cls=SlotDoesNotExistError,
)
transact.put(
item={
'id': enrollment.id,
'sk': 'CANCEL_POLICY',
'created_at': now_,
}
)
if author:
transact.put( transact.put(
item={ item={
'id': enrollment.id, 'id': enrollment.id,
'sk': 'author', 'sk': 'author',
'user_id': author['id'], # Post-migration: uncomment the following line
'name': author['name'], # 'sk': 'created_by',
'user_id': created_by['id'],
'name': created_by['name'],
'created_at': now_, 'created_at': now_,
}, },
) )
if subscription:
transact.put(
item={
'id': enrollment.id,
'sk': 'METADATA#SUBSCRIPTION_COVERED',
'created_at': now_,
}
| subscription,
)
# Prevents the user from enrolling in the same course again until # Prevents the user from enrolling in the same course again until
# the deduplication window expires or is removed. # the deduplication window expires or is removed.
if deduplication_window: if deduplication_window:
offset_days = deduplication_window['offset_days'] offset_days = int(deduplication_window['offset_days'])
ttl_ = ttl( ttl_ = ttl(
start_dt=now_, start_dt=now_,
days=course.access_period - offset_days, days=course.access_period - offset_days,

View File

@@ -18,7 +18,7 @@ from layercake.dynamodb import (
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
from enrollment import DeduplicationWindow, LinkedEntity, enroll from enrollment import LinkedEntity, enroll
from schemas import Course, Enrollment, User from schemas import Course, Enrollment, User
logger = Logger(__name__) logger = Logger(__name__)
@@ -88,12 +88,8 @@ def _handler(record: Course, context: dict) -> Enrollment:
enroll( enroll(
enrollment, enrollment,
persistence_layer=enrollment_layer, persistence_layer=enrollment_layer,
deduplication_window=DeduplicationWindow(offset_days=90), deduplication_window={'offset_days': 90},
linked_entities=frozenset( linked_entities=frozenset({LinkedEntity(context['order_id'], 'ORDER')}),
{
LinkedEntity(context['order_id'], 'ORDER'),
}
),
) )
return enrollment return enrollment

View File

@@ -49,6 +49,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'started_at': started_at.strftime('%d/%m/%Y'), 'started_at': started_at.strftime('%d/%m/%Y'),
'completed_at': completed_at.strftime('%d/%m/%Y'), 'completed_at': completed_at.strftime('%d/%m/%Y'),
'today': _datefmt(now_), 'today': _datefmt(now_),
'year': now_.strftime('%Y'),
}, },
}, },
) )

View File

@@ -1,3 +1,5 @@
from uuid import uuid4
from aws_lambda_powertools import Logger from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_classes import ( from aws_lambda_powertools.utilities.data_classes import (
EventBridgeEvent, EventBridgeEvent,
@@ -10,6 +12,8 @@ from boto3clients import dynamodb_client
from config import ( from config import (
ENROLLMENT_TABLE, ENROLLMENT_TABLE,
) )
from enrollment import LinkedEntity, enroll
from schemas import Course, Enrollment, User
logger = Logger(__name__) logger = Logger(__name__)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@@ -19,11 +23,31 @@ enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@logger.inject_lambda_context @logger.inject_lambda_context
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] new_image = event.detail['new_image']
data = enrollment_layer.collection.get_items( metadata = enrollment_layer.collection.get_items(
TransactKey(new_image['id']) TransactKey(new_image['id'])
+ SortKey('METADATA#SUBSCRIPTION_COVERED', rename_key='subscription') + SortKey('METADATA#SUBSCRIPTION_COVERED', rename_key='subscription')
+ SortKey('author', rename_key='created_by') + SortKey('METADATA#COURSE', rename_key='course')
+ SortKey('tenant', rename_key='org') + SortKey(
'METADATA#DEDUPLICATION_WINDOW',
path_spec='offset_days',
rename_key='dedup_window_offset_days',
)
+ SortKey('konviva')
+ SortKey('tenant', rename_key='org'),
# Post-migration: uncomment the following lines
# + SortKey('KONVIVA', rename_key='konviva')
# + SortKey('ORG', rename_key='org'),
flatten_top=False,
) )
user = User.model_validate(new_image['user'])
course = Course.model_validate(new_image['course'] | metadata['course'])
enrollment = Enrollment(id=uuid4(), course=course, user=user)
subscription = metadata['subscription'] if 'subscription' in metadata else None
return True return enroll(
enrollment,
subscription=subscription,
deduplication_window={'offset_days': metadata['dedup_window_offset_days']},
linked_entities=frozenset({LinkedEntity(new_image['id'], 'ENROLLMENT')}),
persistence_layer=enrollment_layer,
)

View File

@@ -1,500 +0,0 @@
<!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

@@ -15,4 +15,6 @@ def test_reminder_access_period_before_30_days(
} }
} }
app.send_email = lambda *args, **kwargs: ...
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore

View File

@@ -14,5 +14,6 @@ def test_reminder_cert_expiration_before_30_days(
} }
} }
} }
app.send_email = lambda *args, **kwargs: ...
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore

View File

@@ -15,4 +15,6 @@ def test_reminder_no_access_after_3_days(
} }
} }
app.send_email = lambda *args, **kwargs: ...
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore

View File

@@ -15,4 +15,6 @@ def test_reminder_no_activity_after_7_days(
} }
} }
app.send_email = lambda *args, **kwargs: ...
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore

View File

@@ -0,0 +1,30 @@
import app.events.reenroll_if_failed as app
from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer
def test_enroll(
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
lambda_context: LambdaContext,
):
event = {
'detail': {
'new_image': {
'id': '294e9864-8284-4287-b153-927b15d90900',
'sk': '0',
'user': {
'id': '2beb8642-aab4-4088-86d4-2966fac7c570',
'name': 'Gary Pihl',
'email': 'gary@boston.com',
'cpf': '83152103000',
},
'course': {
'id': '62bea9b7-971d-4ee8-ab56-942dc7ca6fcd',
'name': 'pytest',
},
}
}
}
enrollment = app.lambda_handler(event, lambda_context) # type: ignore
print(enrollment)

View File

@@ -18,7 +18,7 @@ def test_schedule_reminders(
'sk': '0', 'sk': '0',
'user': { 'user': {
'id': '1234', 'id': '1234',
'name': 'Ozzy Orbourne', 'name': 'Ozzy Osbourne',
'email': 'ozzy@osbourne.com', 'email': 'ozzy@osbourne.com',
}, },
'course': { 'course': {

View File

@@ -25,6 +25,12 @@
{"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": "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": "METADATA#COURSE", "cert": {"s3_uri": "s3://saladeaula.digital/certs/samples/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": "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"} {"id": "1ee108ae-67d4-4545-bf6d-4e641cdaa4e0", "sk": "COMPLETED", "completed_at": "2025-08-31T21:59:10.842467-03:00"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "0"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#SUBSCRIPTION_COVERED", "updated_at": "2025-09-09T15:20:17.187461-03:00", "billing_day": 1, "org_id": "123", "created_at": "2025-09-09T15:20:14.021500-03:00", "billing_period": "START#2025-09-01#END#2025-09-30"}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#COURSE", "access_period": 360, "created_at": "2025-09-09T09:11:29.292760-03:00", "cert": {"exp_interval": 360}}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "konviva", "class_id": 34, "user_id": 26943, "created_at": "2025-09-09T09:11:29.315247-03:00", "enrollment_id": 244488}
{"id": "294e9864-8284-4287-b153-927b15d90900", "sk": "METADATA#DEDUPLICATION_WINDOW", "offset_days": 90, "created_at": "2025-09-11T09:00:45.923035-03:00"}

View File

@@ -125,7 +125,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'enrolled_at': enrollment['created_at'], 'enrolled_at': enrollment['created_at'],
'created_at': now_, 'created_at': now_,
} }
# Add author if present # Add canceled_by if present
| ( | (
{ {
'author': { 'author': {