add user-management

This commit is contained in:
2025-05-29 13:28:54 -03:00
parent 797a325cb0
commit 590cf10777
35 changed files with 5275 additions and 223 deletions

5
user-management/Makefile Normal file
View File

@@ -0,0 +1,5 @@
build:
sam build --use-container
deploy: build
sam deploy --debug

View File

@@ -0,0 +1,3 @@
import boto3
s3_client = boto3.client('s3')

View File

@@ -0,0 +1 @@
CHUNK_SIZE = 50

View 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

View File

View 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

View 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

View 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'))

View 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
View 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)

View 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" }

View File

@@ -0,0 +1,3 @@
{
"extraPaths": ["app/"]
}

View 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 = []

View 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

View File

View 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()

View File

View 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

View 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'}

File diff suppressed because it is too large Load Diff

View 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
1 CADASTRO DE COLABORADOR
2 NOME COMPLETO EMAIL (letra minúscula) CPF TREINAMENTO
3 ANDRE HENRIQUE LOPES ZAFALON henrique.zafalon@fanucamerica.com 261.955.138-22 NR-35 (RECICLAGEM)
4 SERGIO DA SILVA CUPERTINO sergio.cupertino@fanucamerica.com 066.945.708-64 NR-10 (RECICLAGEM)
5 SERGIO DA SILVA CUPERTINO sergio.cupertino@fanucamerica.com 066.945.708-64 NR-35 (RECICLAGEM)
6 ROVANE CAMPOS rovane.campos@fanucamerica.com 095.958.578-82 NR-10 (RECICLAGEM)
7 ROVANE CAMPOS rovane.campos@fanucamerica.com 095.958.578-82 NR-35 (RECICLAGEM)
8 MARCIO ATSUSHI KANEKO MASUDA marcio.masuda@fanucamerica.com 293.042.798-10 NR-10 (RECICLAGEM)
9 FABIO AKIRA HARAGUCHI fabio.haraguchi@fanucamerica.com 287.018.428-03 NR-10 (RECICLAGEM)
10 EMIDIO YOITI MOCHIZUKI emidio.mochizuki@fanucamerica.com 268.579.208-26 NR-10 (RECICLAGEM)
11 EMIDIO YOITI MOCHIZUKI emidio.mochizuki@fanucamerica.com 268.579.208-26 NR-35 (RECICLAGEM)
12 ERIC HIDEKI MORIKIO eric.morikio@fanucamerica.com 417.359.838-61 NR-10 (RECICLAGEM)
13 HENRIQUE DE FIGUEIREDO BASTOS FERRAZ henrique.ferraz@fanucamerica.com 417.059.788-51 NR-10 (RECICLAGEM)
14 LAYS MORETTI DA SILVA lays.silva@fanucamerica.com 013.107.662-07 NR-10 (RECICLAGEM)
15 LAYS MORETTI DA SILVA lays.silva@fanucamerica.com 013.107.662-07 NR-12
16 ANDRE DE SOUZA andre.souza@fanucamerica.com 290.688.648-31 NR-10 (RECICLAGEM)
17 ANDRE DE SOUZA andre.souza@fanucamerica.com 290.688.648-31 NR-12
18 RAFAEL TOSHIO BURATO MAEDA rafael.maeda@fanucamerica.com 394.153.268-59 NR-10 (RECICLAGEM)
19 RAFAEL TOSHIO BURATO MAEDA rafael.maeda@fanucamerica.com 394.153.268-59 NR-12
20 RAFAEL TOSHIO BURATO MAEDA rafael.maeda@fanucamerica.com 394.153.268-59 NR-35 (RECICLAGEM)
21 RICARDO GALLES BONET ricardo.bonet@fanucamerica.com 424.430.528-93 NR-10 (RECICLAGEM)
22 RULIO SIEFERT SERA rulio.sera@fanucamerica.com 063.916.859-08 NR-10 (RECICLAGEM)
23 MACIEL FERREIRA BOMFIM maciel.bomfim@fanucamerica.com 334.547.088-85 NR-10 (RECICLAGEM)
24 JAIME EDUARDO GALVEZ AVILES jaime.galvez@fanucamerica.com 280.238.818-50 NR-12
25 JAIME EDUARDO GALVEZ AVILES jaime.galvez@fanucamerica.com 280.238.818-50 NR-35 (RECICLAGEM)
26 HIGOR MACHADO SILVA higor.silva@fanucamerica.com 419.879.878-88 NR-12
27 LÁZARO SOUZA DIAS lazaro.dias@fanucamerica.com 067.179.825-19 NR-12
28 JOÃO PEDRO AGUIAR GALASSO joao.pedro@fanucamerica.com 570.403.588-40 NR-12

View 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

File diff suppressed because it is too large Load Diff