add user-management
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Bon de commande</title>
|
||||
<link href="style.css" rel="stylesheet" />
|
||||
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
|
||||
<meta name="dcterms.created" content="{{ dcterms.created }}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="cover">
|
||||
<img src="eduseg.svg" style="width: 14rem" />
|
||||
|
||||
<p>Certificamos que</p>
|
||||
<h1>{{ name }}</h1>
|
||||
<p>
|
||||
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
|
||||
de <strong>NR-10 Complementar (SEP)</strong> com aproveitamento
|
||||
de
|
||||
<strong>{{ progress }}%</strong>
|
||||
</p>
|
||||
<p>Realizado entre {{ start_date }} e {{ finish_date }}</p>
|
||||
<p>Florianópolis, SC, {{ today }}</p>
|
||||
</section>
|
||||
|
||||
<section id="back">
|
||||
<div class="space-y">
|
||||
<h1>Conteúdo programático ministrado</h1>
|
||||
<ul>
|
||||
<li>Organização do sistema elétrico de potência</li>
|
||||
<li>Organização do trabalho</li>
|
||||
<li>Aspectos comportamentais</li>
|
||||
<li>Condições impeditivas para serviços</li>
|
||||
<li>Riscos típicos no SEP e sua prevenção</li>
|
||||
<li>Técnicas de análise de riscos no SEP</li>
|
||||
<li>Procedimentos de trabalho (análise e discussão)</li>
|
||||
<li>Técnicas de análise de riscos no SEP</li>
|
||||
<li>Equipamentos e ferramentas de trabalho</li>
|
||||
<li>Sistemas de proteção coletiva</li>
|
||||
<li>Equipamentos de proteção individual</li>
|
||||
<li>Posturas e vestuários de trabalhos</li>
|
||||
<li>
|
||||
Segurança com veículos e transporte de pessoas,
|
||||
materiais e equipamentos
|
||||
</li>
|
||||
<li>Sinalização e isolamento de áreas de trabalho</li>
|
||||
<li>
|
||||
Liberação de instalação para serviço, operação e uso
|
||||
</li>
|
||||
<li>
|
||||
Treinamento em técnicas de remoção, atendimento e
|
||||
transporte de acidentados
|
||||
</li>
|
||||
<li>Acidentes típicos</li>
|
||||
<li>Responsabilidades</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y">
|
||||
<dd>
|
||||
<h2>Carga horária</h2>
|
||||
<p>40 horas</p>
|
||||
</dd>
|
||||
|
||||
<dd>
|
||||
<h2>Instrutor e responsável técnico</h2>
|
||||
<p>Francis Ricardo Baretta</p>
|
||||
<p>CPF 039.539.409-02</p>
|
||||
<p>Eng. de Segurança no Trabalho Eng. Eletricista</p>
|
||||
<p>CREA/SC 126693-0</p>
|
||||
</dd>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
0
certs/conf.py
Normal file
0
certs/conf.py
Normal file
@@ -1,59 +0,0 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1072.73 329.6"
|
||||
>
|
||||
<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="#f9f7e8"
|
||||
d="M244.7,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
|
||||
></path>
|
||||
<path
|
||||
fill="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
d="M784.56,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
|
||||
></path>
|
||||
<path
|
||||
fill="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
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>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -8,7 +8,7 @@ from weasyprint import HTML
|
||||
locale.setlocale(locale.LC_TIME, 'pt_BR')
|
||||
today = date.today()
|
||||
|
||||
with open('cert.html', encoding='utf-8') as f:
|
||||
with open('nr10_complementar_sep.html', encoding='utf-8') as f:
|
||||
html = f.read()
|
||||
|
||||
|
||||
@@ -25,9 +25,8 @@ html_rendered = template.render(
|
||||
progress=91.99,
|
||||
course='NR-10 Complementar (SEP)',
|
||||
today=today.strftime('%-d de %B de %Y'),
|
||||
start_date=today.strftime('%d/%m/%Y'),
|
||||
finish_date=today.strftime('%d/%m/%Y'),
|
||||
due_date=today.strftime('%d/%m/%Y'),
|
||||
started_date=today.strftime('%d/%m/%Y'),
|
||||
finished_date=today.strftime('%d/%m/%Y'),
|
||||
)
|
||||
|
||||
HTML(string=html_rendered, base_url='').write_pdf('cert.pdf')
|
||||
|
||||
212
certs/nr10_complementar_sep.html
Normal file
212
certs/nr10_complementar_sep.html
Normal file
@@ -0,0 +1,212 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>NR-10 Complementar (SEP)</title>
|
||||
<link href="style.css" rel="stylesheet" />
|
||||
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
|
||||
<meta name="dcterms.created" content="{{ dcterms.created }}" />
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
h1,
|
||||
p,
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SF-Pro";
|
||||
src: url("fonts/SF-Pro.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: SF-Pro;
|
||||
font-size: 13pt;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
section {
|
||||
width: 29.7cm;
|
||||
height: 21cm;
|
||||
break-after: page;
|
||||
box-sizing: border-box;
|
||||
padding: 5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#cover {
|
||||
background-color: #a7e400;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#cover h1 {
|
||||
font-weight: bolder;
|
||||
font-size: 24pt;
|
||||
}
|
||||
|
||||
#back {
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#back h1,
|
||||
#back h2 {
|
||||
font-weight: bold;
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
#back ul {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.space-y > :not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="cover">
|
||||
<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="#f9f7e8"
|
||||
d="M244.7,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
|
||||
></path>
|
||||
<path
|
||||
fill="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
d="M784.56,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
|
||||
></path>
|
||||
<path
|
||||
fill="#f9f7e8"
|
||||
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="#f9f7e8"
|
||||
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>
|
||||
|
||||
<p>Certificamos que</p>
|
||||
<h1>{{ name }}</h1>
|
||||
<p>
|
||||
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
|
||||
de <strong>NR-10 Complementar (SEP)</strong> com aproveitamento
|
||||
de
|
||||
<strong>{{ progress }}%</strong>
|
||||
</p>
|
||||
<p>Realizado entre {{ start_date }} e {{ finish_date }}</p>
|
||||
<p>Florianópolis, SC, {{ today }}</p>
|
||||
</section>
|
||||
|
||||
<section id="back">
|
||||
<div class="space-y">
|
||||
<h1>Conteúdo programático ministrado</h1>
|
||||
<ul>
|
||||
<li>Organização do sistema elétrico de potência</li>
|
||||
<li>Organização do trabalho</li>
|
||||
<li>Aspectos comportamentais</li>
|
||||
<li>Condições impeditivas para serviços</li>
|
||||
<li>Riscos típicos no SEP e sua prevenção</li>
|
||||
<li>Técnicas de análise de riscos no SEP</li>
|
||||
<li>Procedimentos de trabalho (análise e discussão)</li>
|
||||
<li>Técnicas de análise de riscos no SEP</li>
|
||||
<li>Equipamentos e ferramentas de trabalho</li>
|
||||
<li>Sistemas de proteção coletiva</li>
|
||||
<li>Equipamentos de proteção individual</li>
|
||||
<li>Posturas e vestuários de trabalhos</li>
|
||||
<li>
|
||||
Segurança com veículos e transporte de pessoas,
|
||||
materiais e equipamentos
|
||||
</li>
|
||||
<li>Sinalização e isolamento de áreas de trabalho</li>
|
||||
<li>
|
||||
Liberação de instalação para serviço, operação e uso
|
||||
</li>
|
||||
<li>
|
||||
Treinamento em técnicas de remoção, atendimento e
|
||||
transporte de acidentados
|
||||
</li>
|
||||
<li>Acidentes típicos</li>
|
||||
<li>Responsabilidades</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y">
|
||||
<dd>
|
||||
<h2>Carga horária</h2>
|
||||
<p>40 horas</p>
|
||||
</dd>
|
||||
|
||||
<dd>
|
||||
<h2>Instrutor e responsável técnico</h2>
|
||||
<p>Francis Ricardo Baretta</p>
|
||||
<p>CPF 039.539.409-02</p>
|
||||
<p>Eng. de Segurança no Trabalho Eng. Eletricista</p>
|
||||
<p>CREA/SC 126693-0</p>
|
||||
</dd>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,84 +0,0 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SF-Pro";
|
||||
src: url("fonts/SF-Pro.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: SF-Pro;
|
||||
font-size: 13pt;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
section {
|
||||
width: 29.7cm;
|
||||
height: 21cm;
|
||||
break-after: page;
|
||||
box-sizing: border-box;
|
||||
padding: 5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#cover {
|
||||
background-color: #a7e400;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#cover h1 {
|
||||
font-weight: bolder;
|
||||
font-size: 24pt;
|
||||
}
|
||||
|
||||
#back {
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#back h1,
|
||||
#back h2 {
|
||||
font-weight: bold;
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
#back ul {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.space-y > :not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
5
enrollment-management/app/config.py
Normal file
5
enrollment-management/app/config.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
|
||||
ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
|
||||
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
||||
@@ -0,0 +1,14 @@
|
||||
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
|
||||
|
||||
logger = Logger(__name__)
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
@logger.inject_lambda_context
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> None:
|
||||
"""Send an email when the access period expires"""
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
|
||||
logger = Logger(__name__)
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
@logger.inject_lambda_context
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> None: ...
|
||||
48
enrollment-management/app/events/set_status_as_expired.py
Normal file
48
enrollment-management/app/events/set_status_as_expired.py
Normal file
@@ -0,0 +1,48 @@
|
||||
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.dateutils import now
|
||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||
|
||||
from boto3clients import dynamodb_client
|
||||
from config import ENROLLMENT_TABLE
|
||||
|
||||
logger = Logger(__name__)
|
||||
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
@logger.inject_lambda_context
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
new_image = event.detail['new_image']
|
||||
now_ = now()
|
||||
|
||||
with enrollment_layer.transact_items() as transact:
|
||||
transact.update(
|
||||
key=KeyPair(new_image['id'], '0'),
|
||||
update_expr='SET #status = :expired, update_date = :update_date',
|
||||
cond_expr='#status IN (:pending, :in_progress)',
|
||||
expr_attr_names={
|
||||
'#status': 'status',
|
||||
},
|
||||
expr_attr_values={
|
||||
':expired': 'EXPIRED',
|
||||
':pending': 'PENDING',
|
||||
':in_progress': 'IN_PROGRESS',
|
||||
':update_date': now_,
|
||||
},
|
||||
)
|
||||
transact.put(
|
||||
item={
|
||||
'id': new_image['id'],
|
||||
'sk': 'expired_date',
|
||||
'create_date': now_,
|
||||
},
|
||||
)
|
||||
|
||||
transact.write_items()
|
||||
|
||||
return True
|
||||
5
order-management/app/config.py
Normal file
5
order-management/app/config.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
USER_TABLE: str = os.getenv('USER_TABLE') # type: ignore
|
||||
ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
|
||||
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
||||
5
user-management/Makefile
Normal file
5
user-management/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
build:
|
||||
sam build --use-container
|
||||
|
||||
deploy: build
|
||||
sam deploy --debug
|
||||
3
user-management/app/boto3clients.py
Normal file
3
user-management/app/boto3clients.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import boto3
|
||||
|
||||
s3_client = boto3.client('s3')
|
||||
1
user-management/app/config.py
Normal file
1
user-management/app/config.py
Normal file
@@ -0,0 +1 @@
|
||||
CHUNK_SIZE = 50
|
||||
83
user-management/app/csv_utils.py
Normal file
83
user-management/app/csv_utils.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import csv
|
||||
from typing import TextIO
|
||||
|
||||
from smart_open import open
|
||||
|
||||
|
||||
def byte_ranges(
|
||||
csvfile: str,
|
||||
chunk_size: int = 100,
|
||||
**kwargs,
|
||||
) -> list[tuple[int, int]]:
|
||||
"""Compute byte ranges for reading a CSV file in fixed-size line chunks.
|
||||
|
||||
Returns pairs (start_byte, end_byte) for each fixed-size group of lines.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
csvfile : str
|
||||
Path to the CSV file, opened in binary mode internally.
|
||||
chunk_size : int, optional
|
||||
Number of lines per chunk. Default is 100.
|
||||
**kwargs :
|
||||
Extra options passed to `open()`, e.g., buffering.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of tuple[int, int]
|
||||
Byte ranges covering each chunk of lines.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> byte_ranges("users.csv", chunk_size=500)
|
||||
[(0, 3125), (3126, 6150), (6151, 9124)]
|
||||
"""
|
||||
line_offsets = [0]
|
||||
|
||||
with open(csvfile, 'rb', **kwargs) as fp:
|
||||
while True:
|
||||
if not fp.readline():
|
||||
break
|
||||
line_offsets.append(fp.tell())
|
||||
|
||||
total_lines = len(line_offsets) - 1
|
||||
byte_ranges = []
|
||||
|
||||
for start_line in range(1, total_lines + 1, chunk_size):
|
||||
# Calculate the end line index, bounded by total lines
|
||||
end_line = min(start_line + chunk_size - 1, total_lines)
|
||||
# Get byte range for this chunk
|
||||
start_byte = line_offsets[start_line - 1]
|
||||
end_byte = line_offsets[end_line] - 1
|
||||
|
||||
byte_ranges.append((start_byte, end_byte))
|
||||
|
||||
return byte_ranges
|
||||
|
||||
|
||||
def detect_delimiter(sample: TextIO) -> str:
|
||||
"""Detect the delimiter character used in a CSV file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sample : TextIO
|
||||
A file-like object opened in text mode (e.g., from `open('file.csv')`).
|
||||
Must be readable and at position 0.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The detected delimiter character (e.g., ',', ';', '\\t').
|
||||
|
||||
Raises
|
||||
------
|
||||
csv.Error
|
||||
If the file cannot be parsed as CSV or delimiter detection fails.
|
||||
ValueError
|
||||
If the file is empty or contains no detectable delimiter.
|
||||
"""
|
||||
sniffer = csv.Sniffer()
|
||||
dialect = sniffer.sniff(sample.read())
|
||||
sample.seek(0)
|
||||
|
||||
return dialect.delimiter
|
||||
0
user-management/app/events/__init__.py
Normal file
0
user-management/app/events/__init__.py
Normal file
0
user-management/app/events/batch/__init__.py
Normal file
0
user-management/app/events/batch/__init__.py
Normal file
20
user-management/app/events/batch/csv_into_chunks.py
Normal file
20
user-management/app/events/batch/csv_into_chunks.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from aws_lambda_powertools.utilities.data_classes import (
|
||||
EventBridgeEvent,
|
||||
event_source,
|
||||
)
|
||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
|
||||
from boto3clients import s3_client
|
||||
from config import CHUNK_SIZE
|
||||
from csv_utils import byte_ranges
|
||||
|
||||
transport_params = {'client': s3_client}
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
new_image = event.detail['new_image']
|
||||
csvfile = new_image['s3uri']
|
||||
pairs = byte_ranges(csvfile, CHUNK_SIZE, transport_params=transport_params)
|
||||
|
||||
return True
|
||||
14
user-management/app/events/batch/excel_to_csv.py
Normal file
14
user-management/app/events/batch/excel_to_csv.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from aws_lambda_powertools.utilities.data_classes import (
|
||||
EventBridgeEvent,
|
||||
event_source,
|
||||
)
|
||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
|
||||
from boto3clients import s3_client
|
||||
|
||||
transport_params = {'client': s3_client}
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
return True
|
||||
49
user-management/app/events/batch/read_csv_chunk.py
Normal file
49
user-management/app/events/batch/read_csv_chunk.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
from aws_lambda_powertools.utilities.data_classes import (
|
||||
EventBridgeEvent,
|
||||
event_source,
|
||||
)
|
||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
|
||||
from boto3clients import s3_client
|
||||
|
||||
transport_params = {'client': s3_client}
|
||||
|
||||
|
||||
@event_source(data_class=EventBridgeEvent)
|
||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||
new_image = event.detail['new_image']
|
||||
csvfile = new_image['s3_uri']
|
||||
|
||||
data = _get_s3_object_range(
|
||||
csvfile,
|
||||
start_byte=new_image['start_byte'],
|
||||
end_byte=new_image['end_byte'],
|
||||
s3_client=s3_client,
|
||||
)
|
||||
reader = csv.reader(data)
|
||||
|
||||
for x in reader:
|
||||
print(x)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _get_s3_object_range(
|
||||
s3_uri: str,
|
||||
*,
|
||||
start_byte: int,
|
||||
end_byte: int,
|
||||
s3_client,
|
||||
) -> StringIO:
|
||||
bucket, key = s3_uri.replace('s3://', '').split('/', 1)
|
||||
|
||||
response = s3_client.get_object(
|
||||
Bucket=bucket,
|
||||
Key=key,
|
||||
Range=f'bytes={start_byte}-{end_byte}',
|
||||
)
|
||||
|
||||
return StringIO(response['Body'].read().decode('utf-8'))
|
||||
45
user-management/app/events/email_receiving.py
Normal file
45
user-management/app/events/email_receiving.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import urllib.parse as urllib_parse
|
||||
from email.utils import parseaddr
|
||||
from typing import Any, Iterator
|
||||
|
||||
from aws_lambda_powertools import Logger
|
||||
from aws_lambda_powertools.utilities.data_classes import SESEvent, event_source
|
||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
|
||||
logger = Logger(__name__)
|
||||
|
||||
|
||||
@logger.inject_lambda_context
|
||||
@event_source(data_class=SESEvent)
|
||||
def lambda_handler(event: SESEvent, context: LambdaContext) -> dict:
|
||||
ses = event.record.ses
|
||||
to = urllib_parse.unquote(ses.receipt.recipients[0]).lower()
|
||||
name, email_from = parseaddr(get_header_value(ses.mail.headers, 'from'))
|
||||
subject = get_header_value(
|
||||
ses.mail.headers,
|
||||
'subject',
|
||||
default='',
|
||||
raise_on_missing=False,
|
||||
)
|
||||
|
||||
if email_from == 'sergio@somosbeta.com.br':
|
||||
return {'disposition': 'CONTINUE'}
|
||||
|
||||
return {'disposition': 'STOP_RULE_SET'}
|
||||
|
||||
|
||||
def get_header_value(
|
||||
headers: Iterator,
|
||||
header_name: str,
|
||||
*,
|
||||
default: Any = None,
|
||||
raise_on_missing: bool = True,
|
||||
) -> str:
|
||||
for header in headers:
|
||||
if header.name.lower() == header_name:
|
||||
return header.value
|
||||
|
||||
if raise_on_missing:
|
||||
raise ValueError(f'{header_name} not found.')
|
||||
|
||||
return default
|
||||
56
user-management/cf.py
Normal file
56
user-management/cf.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "cloudflare"
|
||||
# ]
|
||||
# ///
|
||||
|
||||
from cloudflare import Cloudflare
|
||||
|
||||
CLOUDFLARE_ACCOUNT_ID = '5436b62470020c04b434ad31c3e4cf4e'
|
||||
CLOUDFLARE_API_TOKEN = 'gFndkBJCzH4pRX7mKXokdWfw1xhm8-9FHfvLfhwa'
|
||||
|
||||
|
||||
client = Cloudflare(api_token=CLOUDFLARE_API_TOKEN)
|
||||
|
||||
assistant = """
|
||||
You are a data analysis assistant specialized in identifying Brazilian
|
||||
personal data from CSV files.
|
||||
|
||||
These CSV files may or may not include headers.
|
||||
|
||||
Your task is to analyze the content and identify only three possible
|
||||
data types: 'name', 'cpf', and 'email'.
|
||||
|
||||
Ignore all other fields.
|
||||
"""
|
||||
|
||||
csv_content = """
|
||||
Sérgio Rafael de Siqueira,10,07879819908,osergiosiqueria@gmail.com,cipa
|
||||
Tiago Maciel,12,086.790.049-01,tiago@somosbeta.com.br,nr 10
|
||||
"""
|
||||
|
||||
prompt = f"""
|
||||
Here is a CSV sample:
|
||||
|
||||
{csv_content}
|
||||
|
||||
Your task is to:
|
||||
- Detect which columns most likely contain "name", "cpf", or "email".
|
||||
- Skip any category that is not present in the data.
|
||||
- Return ONLY a valid Python list of tuples, like:
|
||||
[('name', index), ('cpf', index), ('email', index)]
|
||||
- Use the column index that most likely matches each data type,
|
||||
based on frequency and data format.
|
||||
- Don't include explanations, code, or any additional text.
|
||||
"""
|
||||
|
||||
r = client.ai.run(
|
||||
model_name='@cf/meta/llama-3-8b-instruct',
|
||||
account_id=CLOUDFLARE_ACCOUNT_ID,
|
||||
messages=[
|
||||
{'role': 'system', 'content': assistant},
|
||||
{'role': 'user', 'content': prompt},
|
||||
],
|
||||
)
|
||||
|
||||
print(r)
|
||||
31
user-management/pyproject.toml
Normal file
31
user-management/pyproject.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[project]
|
||||
name = "user-management"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
readme = ""
|
||||
requires-python = ">=3.13"
|
||||
dependencies = ["layercake"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8.3.4",
|
||||
"pytest-cov>=6.0.0",
|
||||
"ruff>=0.9.1",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["app/"]
|
||||
addopts = "--cov --cov-report html -v"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py311"
|
||||
src = ["app"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "single"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I"]
|
||||
|
||||
[tool.uv.sources]
|
||||
layercake = { path = "../layercake" }
|
||||
3
user-management/pyrightconfig.json
Normal file
3
user-management/pyrightconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraPaths": ["app/"]
|
||||
}
|
||||
9
user-management/samconfig.toml
Normal file
9
user-management/samconfig.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
version = 0.1
|
||||
[default.deploy.parameters]
|
||||
stack_name = "saladeaula-user-management"
|
||||
resolve_s3 = true
|
||||
s3_prefix = "user_management"
|
||||
region = "sa-east-1"
|
||||
confirm_changeset = false
|
||||
capabilities = "CAPABILITY_IAM"
|
||||
image_repositories = []
|
||||
110
user-management/template.yaml
Normal file
110
user-management/template.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
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
|
||||
|
||||
Globals:
|
||||
Function:
|
||||
CodeUri: app/
|
||||
Runtime: python3.13
|
||||
Tracing: Active
|
||||
Architectures:
|
||||
- x86_64
|
||||
Layers:
|
||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:72
|
||||
Environment:
|
||||
Variables:
|
||||
TZ: America/Sao_Paulo
|
||||
LOG_LEVEL: DEBUG
|
||||
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
|
||||
POWERTOOLS_LOGGER_LOG_EVENT: true
|
||||
USER_TABLE: !Ref UserTable
|
||||
|
||||
Resources:
|
||||
EventLog:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
RetentionInDays: 90
|
||||
|
||||
EventCsvChunksFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
Handler: events.batch.csv_chunks.lambda_handler
|
||||
LoggingConfig:
|
||||
LogGroup: !Ref EventLog
|
||||
Policies:
|
||||
- S3CrudPolicy:
|
||||
BucketName: !Ref BucketName
|
||||
Events:
|
||||
DynamoDBEvent:
|
||||
Type: EventBridgeRule
|
||||
Properties:
|
||||
Pattern:
|
||||
resources: [betaeducacao-prod-users_d2o3r5gmm4it7j]
|
||||
detail:
|
||||
new_image:
|
||||
sk:
|
||||
- prefix: batch_jobs#
|
||||
|
||||
EventEmailReceivingFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
Handler: events.email_receiving.lambda_handler
|
||||
LoggingConfig:
|
||||
LogGroup: !Ref EventLog
|
||||
|
||||
LambdaInvokePermission:
|
||||
Type: AWS::Lambda::Permission
|
||||
Properties:
|
||||
FunctionName: !GetAtt EventEmailReceivingFunction.Arn
|
||||
Action: lambda:InvokeFunction
|
||||
Principal: ses.amazonaws.com
|
||||
SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:receipt-rule-set/*
|
||||
|
||||
BucketPolicy:
|
||||
Type: AWS::S3::BucketPolicy
|
||||
Properties:
|
||||
Bucket: !Ref BucketName
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: ses.amazonaws.com
|
||||
Action: s3:PutObject
|
||||
Resource: !Sub arn:aws:s3:::${BucketName}/*
|
||||
Condition:
|
||||
StringEquals:
|
||||
aws:SourceAccount: !Ref AWS::AccountId
|
||||
StringLike:
|
||||
aws:SourceArn: !Sub arn:aws:ses:${AWS::Region}:${AWS::AccountId}:receipt-rule-set/*
|
||||
|
||||
EmailReceiptRuleSet:
|
||||
Type: AWS::SES::ReceiptRuleSet
|
||||
Properties:
|
||||
RuleSetName: users.noreply.saladeaula.digital
|
||||
|
||||
EmailReceiptRule:
|
||||
Type: AWS::SES::ReceiptRule
|
||||
DependsOn:
|
||||
- LambdaInvokePermission
|
||||
- BucketPolicy
|
||||
Properties:
|
||||
RuleSetName: !Ref EmailReceiptRuleSet
|
||||
Rule:
|
||||
Name: lambda
|
||||
Enabled: true
|
||||
Actions:
|
||||
- LambdaAction:
|
||||
FunctionArn: !GetAtt EventEmailReceivingFunction.Arn
|
||||
InvocationType: RequestResponse
|
||||
- S3Action:
|
||||
BucketName: !Ref BucketName
|
||||
ObjectKeyPrefix: "mailbox"
|
||||
ScanEnabled: true
|
||||
0
user-management/tests/__init__.py
Normal file
0
user-management/tests/__init__.py
Normal file
16
user-management/tests/conftest.py
Normal file
16
user-management/tests/conftest.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@dataclass
|
||||
class LambdaContext:
|
||||
function_name: str = 'test'
|
||||
memory_limit_in_mb: int = 128
|
||||
invoked_function_arn: str = 'arn:aws:lambda:eu-west-1:809313241:function:test'
|
||||
aws_request_id: str = '52fdfc07-2182-154f-163f-5f0f9a621d72'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lambda_context() -> LambdaContext:
|
||||
return LambdaContext()
|
||||
0
user-management/tests/events/__init__.py
Normal file
0
user-management/tests/events/__init__.py
Normal file
13
user-management/tests/events/batch/test_csv_into_chunks.py
Normal file
13
user-management/tests/events/batch/test_csv_into_chunks.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import events.batch.csv_into_chunks as app
|
||||
|
||||
|
||||
def test_chunk_csv(lambda_context):
|
||||
event = {
|
||||
'detail': {
|
||||
'new_image': {
|
||||
's3uri': 's3://saladeaula.digital/samples/large_users.csv',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.lambda_handler(event, lambda_context) # type: ignore
|
||||
136
user-management/tests/events/test_email_receiving.py
Normal file
136
user-management/tests/events/test_email_receiving.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||
|
||||
import events.email_receiving as app
|
||||
|
||||
event = {
|
||||
'Records': [
|
||||
{
|
||||
'eventSource': 'aws:ses',
|
||||
'eventVersion': '1.0',
|
||||
'ses': {
|
||||
'mail': {
|
||||
'timestamp': '2025-05-29T15:50:41.604Z',
|
||||
'source': 'sergio@somosbeta.com.br',
|
||||
'messageId': '2994higq3tr7efijr3lj65etntffapgg1q7hea81',
|
||||
'destination': [
|
||||
'org+35980592000130@users.noreply.saladeaula.digital'
|
||||
],
|
||||
'headersTruncated': False,
|
||||
'headers': [
|
||||
{'name': 'Return-Path', 'value': '<sergio@somosbeta.com.br>'},
|
||||
{
|
||||
'name': 'Received',
|
||||
'value': 'from mail-lf1-f54.google.com (mail-lf1-f54.google.com [209.85.167.54]) by inbound-smtp.sa-east-1.amazonaws.com with SMTP id 2994higq3tr7efijr3lj65etntffapgg1q7hea81 for org+35980592000130@users.noreply.saladeaula.digital; Thu, 29 May 2025 15:50:41 +0000 (UTC)',
|
||||
},
|
||||
{'name': 'X-SES-Spam-Verdict', 'value': 'PASS'},
|
||||
{'name': 'X-SES-Virus-Verdict', 'value': 'PASS'},
|
||||
{
|
||||
'name': 'Received-SPF',
|
||||
'value': 'pass (spfCheck: domain of somosbeta.com.br designates 209.85.167.54 as permitted sender) client-ip=209.85.167.54; envelope-from=sergio@somosbeta.com.br; helo=mail-lf1-f54.google.com;',
|
||||
},
|
||||
{
|
||||
'name': 'Authentication-Results',
|
||||
'value': 'amazonses.com; spf=pass (spfCheck: domain of somosbeta.com.br designates 209.85.167.54 as permitted sender) client-ip=209.85.167.54; envelope-from=sergio@somosbeta.com.br; helo=mail-lf1-f54.google.com; dkim=pass header.i=@somosbeta.com.br; dmarc=none header.from=somosbeta.com.br;',
|
||||
},
|
||||
{
|
||||
'name': 'X-SES-RECEIPT',
|
||||
'value': 'AEFBQUFBQUFBQUFHVWpuODdPY2tGUlordE5YWkVEUlZNWWZFYkpDMU5MUURyaHNVSldnTGhEVWhCQzd5UGpzWHI4LzJoS1VaN0lOU0FkMzJFU0h6MjVuUzk2c09KUXlzbUJQdHd6T0d0Y2ptZXhRVk1KY3RkOXpRamZMb3hwSGJIVlFla2tBcmZvRmYwQS9WU3hBVlBqcUpDYm00eTdiRnRqNW45ek9ld0ZyTGJKV3k2TXRpc0J6aGhBdmFvZDFDQ000Zm9QTng3VHljNXArM0hjT2ZsYkhtM3RCZnpRV1NOczU2RDdmL0RKclJOcDNvY2ZxV1hmajNYMkczVHpsWEZCMm40Z2pQM29udkMyb01vN3JwU0p2TUI1WGorN2JPd2RPYW5lUDN3T3RMRlhsdEpGbGNCa3c9PQ==',
|
||||
},
|
||||
{
|
||||
'name': 'X-SES-DKIM-SIGNATURE',
|
||||
'value': 'a=rsa-sha256; q=dns/txt; b=KPtFiBwsOTBl1YVLRTSfaZ+X6h7uSSOu/i1Cw6Pd+wvMBHRWy9EYcWUjyDjsLG/uYHShLW4+LHsSg9HiqrAP2jVJSAawrIwZr1wPQo7ovQvWuZfHQN/StgXIgBU+L7Bp6GSR26LRufxjj7q9YBmEeirjJ3d0G8E/rF2QqeITlpo=; c=relaxed/simple; s=bm3ypaoivbtdzmy3b6w37fzb5voa2uru; d=amazonses.com; t=1748533842; v=1; bh=kTUCV1DQAazu4FsUi1MrelD2QvSfHGArZ/c6A79t3/E=; h=From:To:Cc:Bcc:Subject:Date:Message-ID:MIME-Version:Content-Type:X-SES-RECEIPT;',
|
||||
},
|
||||
{
|
||||
'name': 'Received',
|
||||
'value': 'by mail-lf1-f54.google.com with SMTP id 2adb3069b0e04-54b0d638e86so1570269e87.1 for <org+35980592000130@users.noreply.saladeaula.digital>; Thu, 29 May 2025 08:50:40 -0700 (PDT)',
|
||||
},
|
||||
{
|
||||
'name': 'DKIM-Signature',
|
||||
'value': 'v=1; a=rsa-sha256; c=relaxed/relaxed; d=somosbeta.com.br; s=google; t=1748533838; x=1749138638; darn=users.noreply.saladeaula.digital; h=to:subject:message-id:date:from:in-reply-to:references:mime-version:from:to:cc:subject:date:message-id:reply-to; bh=kTUCV1DQAazu4FsUi1MrelD2QvSfHGArZ/c6A79t3/E=; b=Qi8gk/kTpwXCLDM7FPS7ULTy+9gO/4WsGL9zY1xEDw0Rp38f4rVR8L95hIhwK2daA27mq3pv9TdrK3XKQQIuSvRVvaM0b/evkZD8QhaT9tCmL0eKEBB4czGB0OSS3Q4qP34GFWMmXIaxoKIo1td76JnXbto9ZQvjUTBr3GGlF3Lm/MPTaAHs1b3dalv2diTvyj1tzoeb4wGePKsqLh5LKGOxbbWsxPeHEJ8sLM4LyJjxoqSOO0wgKdH5S/ZNpHWcJtXBntjiDUZNeQ5ucEn8ZLbADCObZZV/gH9i/cB1BmlSvJP3D07uJTAEBqyepd+W9fIW2mox/+fmOb3OEHRthQ==',
|
||||
},
|
||||
{
|
||||
'name': 'X-Google-DKIM-Signature',
|
||||
'value': 'v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1748533838; x=1749138638; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=kTUCV1DQAazu4FsUi1MrelD2QvSfHGArZ/c6A79t3/E=; b=UnL/uRXRahnH5uStZ276LH4kqpHigngw4iql9GHKmFaIKxJ8hLGn/wu7ie4ljnw8m/ I4CvhDKH4TVtIWPS81fm06PMgeqQYRX3jLhHvIltROCNVX6ZFzbAXgiAlk0NS1npvDYJ evVgSaPPco4D/8pMWZX4fUjU+8me32ChKxHsklEAts/LiD+MvTuHCHZovSEj1aXAz91b yGZe1bx2+phuqzUZyIOeheKjl4TNjEBx83omOzf9HtClKhjzCwHjfZ8uk2lhJ10ogKZ0 GNQ5OlnPkgdAg0/+HsifvGR6xfkFsiunIDyinBWoOhMU1o0+DiicxOIjY8QEayF3MLUt REoA==',
|
||||
},
|
||||
{
|
||||
'name': 'X-Gm-Message-State',
|
||||
'value': 'AOJu0Yxw0icQkFV090vn5hx/hKp0ePH78Wr0iqi4V3x4mpVXrRX8te2o 30aBYeZRPwn8SRSrq/kbn4bLcs5mPDB+iRP9IGFxS7KLSQi+KG4PQeDHyW3R/AgOPHACUUXUUyz Vcwny029WGY5PVhxlikAYdDfhNdO8GM2DKMV1+Oxy/a+qmt5LZeuy',
|
||||
},
|
||||
{
|
||||
'name': 'X-Gm-Gg',
|
||||
'value': 'ASbGncslCMPPU/pax0+RNy/cQR/Y/wUroSJMvI2DCCMq6Qld+Ih1jG4+HnhQPqn3nTK EEW6/99tqazq+SKy+31AB77ajVczvJQTElRSW/+bhd42l7by2hicTKElcR3GWivlrqd1TywUZOB DkB9J/vupSV0PDCJfZVi+7Tb9Pb61nnxaU+SQ=',
|
||||
},
|
||||
{
|
||||
'name': 'X-Google-Smtp-Source',
|
||||
'value': 'AGHT+IFYi41KmJjGcfQmUvWJDdTAzGIv2JlL9XAwBpAb53mMOOm3tttzkhbvfuiKh/DI9NjITHuO3xuEPqnPI9lpum8=',
|
||||
},
|
||||
{
|
||||
'name': 'X-Received',
|
||||
'value': 'by 2002:a05:6512:1392:b0:553:2f61:58f1 with SMTP id 2adb3069b0e04-5532f615a8dmr2268707e87.53.1748533837647; Thu, 29 May 2025 08:50:37 -0700 (PDT)',
|
||||
},
|
||||
{'name': 'MIME-Version', 'value': '1.0'},
|
||||
{
|
||||
'name': 'References',
|
||||
'value': '<CAMThe4mV9=1-BLiOi9MU3fAS=C6uYE9+3hKUjibrwxxngYNn2Q@mail.gmail.com>',
|
||||
},
|
||||
{
|
||||
'name': 'In-Reply-To',
|
||||
'value': '<CAMThe4mV9=1-BLiOi9MU3fAS=C6uYE9+3hKUjibrwxxngYNn2Q@mail.gmail.com>',
|
||||
},
|
||||
{
|
||||
'name': 'From',
|
||||
'value': 'Sérgio Rafael Siqueira <sergio@somosbeta.com.br>',
|
||||
},
|
||||
{'name': 'Date', 'value': 'Thu, 29 May 2025 12:50:26 -0300'},
|
||||
{
|
||||
'name': 'X-Gm-Features',
|
||||
'value': 'AX0GCFvofROqzf21KTgiIJq_AULCNljEuNFUJBk2xQGwVKmPjim_4slYIOP0WRw',
|
||||
},
|
||||
{
|
||||
'name': 'Message-ID',
|
||||
'value': '<CAMThe4=yMRJg4YOcACYAR509N1RyWyQgAghyVmr=NuSJnbondg@mail.gmail.com>',
|
||||
},
|
||||
{'name': 'Subject', 'value': 'Re: test'},
|
||||
{
|
||||
'name': 'To',
|
||||
'value': 'org+35980592000130@users.noreply.saladeaula.digital',
|
||||
},
|
||||
{
|
||||
'name': 'Content-Type',
|
||||
'value': 'multipart/alternative; boundary="00000000000045b8c206364842b3"',
|
||||
},
|
||||
],
|
||||
'commonHeaders': {
|
||||
'returnPath': 'sergio@somosbeta.com.br',
|
||||
'from': ['"Sérgio Rafael Siqueira" <sergio@somosbeta.com.br>'],
|
||||
'date': 'Thu, 29 May 2025 12:50:26 -0300',
|
||||
'to': ['org+35980592000130@users.noreply.saladeaula.digital'],
|
||||
'messageId': '<CAMThe4=yMRJg4YOcACYAR509N1RyWyQgAghyVmr=NuSJnbondg@mail.gmail.com>',
|
||||
'subject': 'Re: test',
|
||||
},
|
||||
},
|
||||
'receipt': {
|
||||
'timestamp': '2025-05-29T15:50:41.604Z',
|
||||
'processingTimeMillis': 1105,
|
||||
'recipients': [
|
||||
'org+35980592000130@users.noreply.saladeaula.digital'
|
||||
],
|
||||
'spamVerdict': {'status': 'PASS'},
|
||||
'virusVerdict': {'status': 'PASS'},
|
||||
'spfVerdict': {'status': 'PASS'},
|
||||
'dkimVerdict': {'status': 'PASS'},
|
||||
'dmarcVerdict': {'status': 'GRAY'},
|
||||
'action': {
|
||||
'type': 'Lambda',
|
||||
'functionArn': 'arn:aws:lambda:sa-east-1:336641857101:function:saladeaula-user-managemen-EventEmailReceivingFunct-LmnnEfi9tL2O',
|
||||
'invocationType': 'Event',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_email_receiving(lambda_context: LambdaContext):
|
||||
assert app.lambda_handler(event, lambda_context) == {'disposition': 'CONTINUE'}
|
||||
3286
user-management/tests/samples/large_users.csv
Normal file
3286
user-management/tests/samples/large_users.csv
Normal file
File diff suppressed because it is too large
Load Diff
28
user-management/tests/samples/users.csv
Normal file
28
user-management/tests/samples/users.csv
Normal file
@@ -0,0 +1,28 @@
|
||||
CADASTRO DE COLABORADOR,,,,
|
||||
,NOME COMPLETO,EMAIL (letra minúscula),CPF,TREINAMENTO
|
||||
,ANDRE HENRIQUE LOPES ZAFALON,henrique.zafalon@fanucamerica.com,261.955.138-22,NR-35 (RECICLAGEM)
|
||||
,SERGIO DA SILVA CUPERTINO,sergio.cupertino@fanucamerica.com,066.945.708-64,NR-10 (RECICLAGEM)
|
||||
,SERGIO DA SILVA CUPERTINO,sergio.cupertino@fanucamerica.com,066.945.708-64,NR-35 (RECICLAGEM)
|
||||
,ROVANE CAMPOS,rovane.campos@fanucamerica.com,095.958.578-82,NR-10 (RECICLAGEM)
|
||||
,ROVANE CAMPOS,rovane.campos@fanucamerica.com,095.958.578-82,NR-35 (RECICLAGEM)
|
||||
,MARCIO ATSUSHI KANEKO MASUDA,marcio.masuda@fanucamerica.com,293.042.798-10,NR-10 (RECICLAGEM)
|
||||
,FABIO AKIRA HARAGUCHI,fabio.haraguchi@fanucamerica.com,287.018.428-03,NR-10 (RECICLAGEM)
|
||||
,EMIDIO YOITI MOCHIZUKI,emidio.mochizuki@fanucamerica.com,268.579.208-26,NR-10 (RECICLAGEM)
|
||||
,EMIDIO YOITI MOCHIZUKI,emidio.mochizuki@fanucamerica.com,268.579.208-26,NR-35 (RECICLAGEM)
|
||||
,ERIC HIDEKI MORIKIO,eric.morikio@fanucamerica.com,417.359.838-61,NR-10 (RECICLAGEM)
|
||||
,HENRIQUE DE FIGUEIREDO BASTOS FERRAZ,henrique.ferraz@fanucamerica.com,417.059.788-51,NR-10 (RECICLAGEM)
|
||||
,LAYS MORETTI DA SILVA,lays.silva@fanucamerica.com,013.107.662-07,NR-10 (RECICLAGEM)
|
||||
,LAYS MORETTI DA SILVA,lays.silva@fanucamerica.com,013.107.662-07,NR-12
|
||||
,ANDRE DE SOUZA,andre.souza@fanucamerica.com,290.688.648-31,NR-10 (RECICLAGEM)
|
||||
,ANDRE DE SOUZA,andre.souza@fanucamerica.com,290.688.648-31,NR-12
|
||||
,RAFAEL TOSHIO BURATO MAEDA,rafael.maeda@fanucamerica.com,394.153.268-59,NR-10 (RECICLAGEM)
|
||||
,RAFAEL TOSHIO BURATO MAEDA,rafael.maeda@fanucamerica.com,394.153.268-59,NR-12
|
||||
,RAFAEL TOSHIO BURATO MAEDA,rafael.maeda@fanucamerica.com,394.153.268-59,NR-35 (RECICLAGEM)
|
||||
,RICARDO GALLES BONET,ricardo.bonet@fanucamerica.com,424.430.528-93,NR-10 (RECICLAGEM)
|
||||
,RULIO SIEFERT SERA,rulio.sera@fanucamerica.com,063.916.859-08,NR-10 (RECICLAGEM)
|
||||
,MACIEL FERREIRA BOMFIM,maciel.bomfim@fanucamerica.com,334.547.088-85,NR-10 (RECICLAGEM)
|
||||
,JAIME EDUARDO GALVEZ AVILES,jaime.galvez@fanucamerica.com,280.238.818-50,NR-12
|
||||
,JAIME EDUARDO GALVEZ AVILES,jaime.galvez@fanucamerica.com,280.238.818-50,NR-35 (RECICLAGEM)
|
||||
,HIGOR MACHADO SILVA,higor.silva@fanucamerica.com,419.879.878-88,NR-12
|
||||
,LÁZARO SOUZA DIAS,lazaro.dias@fanucamerica.com,067.179.825-19,NR-12
|
||||
,JOÃO PEDRO AGUIAR GALASSO,joao.pedro@fanucamerica.com,570.403.588-40,NR-12
|
||||
|
29
user-management/tests/test_csv_utils.py
Normal file
29
user-management/tests/test_csv_utils.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from csv_utils import byte_ranges, detect_delimiter
|
||||
|
||||
|
||||
def test_detect_delimiter():
|
||||
with open('tests/samples/users.csv') as fp:
|
||||
assert detect_delimiter(fp) == ','
|
||||
|
||||
|
||||
def test_byte_ranges():
|
||||
csvpath = 'tests/samples/users.csv'
|
||||
ranges = byte_ranges(csvpath, 10)
|
||||
*_, pair = ranges
|
||||
start_byte, end_byte = pair
|
||||
|
||||
assert ranges == [(0, 808), (809, 1655), (1656, 2303)]
|
||||
|
||||
expected = """,RICARDO GALLES BONET,ricardo.bonet@fanucamerica.com,424.430.528-93,NR-10 (RECICLAGEM)
|
||||
,RULIO SIEFERT SERA,rulio.sera@fanucamerica.com,063.916.859-08,NR-10 (RECICLAGEM)
|
||||
,MACIEL FERREIRA BOMFIM,maciel.bomfim@fanucamerica.com,334.547.088-85,NR-10 (RECICLAGEM)
|
||||
,JAIME EDUARDO GALVEZ AVILES,jaime.galvez@fanucamerica.com,280.238.818-50,NR-12
|
||||
,JAIME EDUARDO GALVEZ AVILES,jaime.galvez@fanucamerica.com,280.238.818-50,NR-35 (RECICLAGEM)
|
||||
,HIGOR MACHADO SILVA,higor.silva@fanucamerica.com,419.879.878-88,NR-12
|
||||
,LÁZARO SOUZA DIAS,lazaro.dias@fanucamerica.com,067.179.825-19,NR-12
|
||||
,JOÃO PEDRO AGUIAR GALASSO,joao.pedro@fanucamerica.com,570.403.588-40,NR-12"""
|
||||
|
||||
with open(csvpath, 'rb') as f:
|
||||
f.seek(start_byte)
|
||||
data = f.read(end_byte - start_byte + 1)
|
||||
assert data.decode('utf-8') == expected
|
||||
1038
user-management/uv.lock
generated
Normal file
1038
user-management/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user