transdesk-importer-python-sdk 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transdesk_importer_python_sdk-1.0.0/LICENSE +21 -0
- transdesk_importer_python_sdk-1.0.0/PKG-INFO +228 -0
- transdesk_importer_python_sdk-1.0.0/README.md +185 -0
- transdesk_importer_python_sdk-1.0.0/pyproject.toml +42 -0
- transdesk_importer_python_sdk-1.0.0/setup.cfg +4 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/__init__.py +70 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/client.py +42 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/exporters/__init__.py +3 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/exporters/json_exporter.py +25 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/models/__init__.py +59 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/models/canonical.py +124 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/models/internal.py +182 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/readers/__init__.py +3 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/readers/excel_reader.py +77 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/shared/__init__.py +3 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/shared/data_normalizer.py +47 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/__init__.py +0 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/canonical/__init__.py +5 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/canonical/apolice_builder.py +272 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/canonical/cobertura_builder.py +39 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/canonical/item_classifier.py +22 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/internal/__init__.py +6 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/internal/coverage_mapper.py +138 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/internal/internal_mapper.py +176 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/internal/rcf_parser.py +47 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/transformers/internal/towing_parser.py +13 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/validators/__init__.py +3 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk/importer/validators/internal_policy_validator.py +75 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk_importer_python_sdk.egg-info/PKG-INFO +228 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk_importer_python_sdk.egg-info/SOURCES.txt +34 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk_importer_python_sdk.egg-info/dependency_links.txt +1 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk_importer_python_sdk.egg-info/requires.txt +6 -0
- transdesk_importer_python_sdk-1.0.0/src/transdesk_importer_python_sdk.egg-info/top_level.txt +1 -0
- transdesk_importer_python_sdk-1.0.0/tests/test_excel_reader.py +31 -0
- transdesk_importer_python_sdk-1.0.0/tests/test_importer.py +60 -0
- transdesk_importer_python_sdk-1.0.0/tests/test_validator.py +60 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FMConsult
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: transdesk-importer-python-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg
|
|
5
|
+
Author-email: FMConsult <contato@fmconsult.com.br>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 FMConsult
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/fmconsult/77seg-transdesk-importer
|
|
29
|
+
Project-URL: Bug Tracker, https://github.com/fmconsult/77seg-transdesk-importer/issues
|
|
30
|
+
Keywords: sdk,transdesk,importer,apolices,77seg
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Requires-Python: >=3.11
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: pandas>=2.0
|
|
38
|
+
Requires-Dist: openpyxl>=3.1
|
|
39
|
+
Requires-Dist: xlrd>=2.0
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# transdesk-importer-python-sdk
|
|
45
|
+
|
|
46
|
+
SDK Python para leitura e transformacao de planilhas de apolices da **Transdesk**
|
|
47
|
+
no formato interno consumido pela `77seg-core-api`.
|
|
48
|
+
|
|
49
|
+
O SDK e **puro**: ele apenas **le, transforma e valida** a planilha, devolvendo
|
|
50
|
+
modelos tipados. Ele **nao** grava no banco, nao cadastra leads/corretoras e nao
|
|
51
|
+
gera link de assinatura -- essas responsabilidades ficam na core-api
|
|
52
|
+
(ver [Referencia de enrichment](#referencia-de-enrichment-core-api)).
|
|
53
|
+
|
|
54
|
+
## Instalacao
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install transdesk-importer-python-sdk
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Requer Python >= 3.11.
|
|
61
|
+
|
|
62
|
+
## Uso
|
|
63
|
+
|
|
64
|
+
A entrada pode ser `bytes` (upload em memoria), um objeto file-like binario ou
|
|
65
|
+
um caminho de arquivo. O formato (`.xls`/`.xlsx`) e detectado automaticamente
|
|
66
|
+
por magic bytes -- nao depende da extensao.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from transdesk.importer import TransdeskImporter
|
|
70
|
+
|
|
71
|
+
# 1) a partir de bytes (ex.: upload de um endpoint)
|
|
72
|
+
file_bytes = request.files["file"].file.read()
|
|
73
|
+
result = TransdeskImporter().import_policies(file_bytes)
|
|
74
|
+
|
|
75
|
+
# 2) a partir de um caminho
|
|
76
|
+
result = TransdeskImporter().import_policies("apolices.xls")
|
|
77
|
+
|
|
78
|
+
if not result.is_valid:
|
|
79
|
+
# result.errors -> list[ValidationError]
|
|
80
|
+
for err in result.errors:
|
|
81
|
+
print(err)
|
|
82
|
+
else:
|
|
83
|
+
for policy in result.policies: # list[InternalPolicy]
|
|
84
|
+
print(policy.reference_id, len(policy.data.items))
|
|
85
|
+
|
|
86
|
+
# serializacao pronta para JSON
|
|
87
|
+
payload = result.to_dict() # dict
|
|
88
|
+
as_json = result.to_json() # str (ensure_ascii=False)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Retorno
|
|
92
|
+
|
|
93
|
+
`import_policies(file)` devolve um `ImportResult`:
|
|
94
|
+
|
|
95
|
+
| Campo | Tipo | Descricao |
|
|
96
|
+
| ---------- | ----------------------- | ------------------------------------------ |
|
|
97
|
+
| `policies` | `list[InternalPolicy]` | Apolices ja no formato interno |
|
|
98
|
+
| `errors` | `list[ValidationError]` | Divergencias encontradas na validacao |
|
|
99
|
+
| `is_valid` | `bool` | `True` quando `errors` esta vazio |
|
|
100
|
+
|
|
101
|
+
Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
|
|
102
|
+
|
|
103
|
+
### Principais modelos
|
|
104
|
+
|
|
105
|
+
- Saida (EN): `InternalPolicy`, `PolicyData`, `ResellerData`, `Broker`, `Lead`,
|
|
106
|
+
`CustomerInfo`, `Telephone`, `Mobile`, `Address`, `InternalItem`.
|
|
107
|
+
- Canonico (PT, em `InternalPolicy.original_data`): `CanonicalPolicy`,
|
|
108
|
+
`CanonicalItem`, `Subestipulante`, `Cliente`, `Endereco`, `DadosBancarios`,
|
|
109
|
+
`UnidadeVenda`, `Cobertura`, `Complemento`.
|
|
110
|
+
|
|
111
|
+
`InternalItem` tem um schema "achatado" e **consistente**: todos os campos de
|
|
112
|
+
cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
|
|
113
|
+
item (ex.: campos de vida num veiculo) vem como `null`.
|
|
114
|
+
|
|
115
|
+
### Validacao
|
|
116
|
+
|
|
117
|
+
O `ValidationError` cobre tres tipos:
|
|
118
|
+
|
|
119
|
+
- `policy_count` -- divergencia na contagem de apolices;
|
|
120
|
+
- `item_count` -- divergencia na contagem de itens de uma apolice;
|
|
121
|
+
- `premium` -- divergencia (apos arredondamento) entre o premio somado no
|
|
122
|
+
canonico e o premio somado no formato interno de um item.
|
|
123
|
+
|
|
124
|
+
## Arquitetura
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
planilha (bytes/file-like/path)
|
|
128
|
+
|
|
|
129
|
+
v
|
|
130
|
+
ExcelReader -> DataFrame
|
|
131
|
+
|
|
|
132
|
+
v
|
|
133
|
+
ApoliceBuilder -> list[CanonicalPolicy] (camada canonica, PT)
|
|
134
|
+
|
|
|
135
|
+
v
|
|
136
|
+
InternalMapper -> list[InternalPolicy] (camada interna, EN)
|
|
137
|
+
|
|
|
138
|
+
v
|
|
139
|
+
InternalPolicyValidator-> list[ValidationError]
|
|
140
|
+
|
|
|
141
|
+
v
|
|
142
|
+
ImportResult (policies + errors)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Desenvolvimento
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
python -m venv venv && source venv/bin/activate
|
|
149
|
+
pip install -e ".[dev]"
|
|
150
|
+
pytest
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Publicacao: criar uma tag `vX.Y.Z` dispara o workflow de publish no PyPI
|
|
154
|
+
(`.github/workflows/publish.yml`).
|
|
155
|
+
|
|
156
|
+
## Referencia de enrichment (core-api)
|
|
157
|
+
|
|
158
|
+
> Esta secao **nao** faz parte do SDK. Ela preserva, como referencia, a logica
|
|
159
|
+
> de "enrichment" e persistencia que **saiu** do importer e deve ser portada
|
|
160
|
+
> para a `77seg-core-api`.
|
|
161
|
+
|
|
162
|
+
Apos obter as `InternalPolicy` do SDK, a core-api deve:
|
|
163
|
+
|
|
164
|
+
1. **Completar os dados** de cada apolice (cadastrar lead e unidade/corretora,
|
|
165
|
+
injetar `enterprise_id`/`reseller_id` e os defaults da venda);
|
|
166
|
+
2. **Persistir** os registros das vendas no banco;
|
|
167
|
+
3. **Gerar o link de assinatura** (Paperless) e atualizar o status para
|
|
168
|
+
`pending_signature`.
|
|
169
|
+
|
|
170
|
+
Codigo original (removido do SDK), que serve de base para a implementacao na
|
|
171
|
+
core-api:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
class InternalImportPipeline:
|
|
175
|
+
|
|
176
|
+
def __init__(self, enterprise_id, reseller_id):
|
|
177
|
+
self.enterprise_id = enterprise_id
|
|
178
|
+
self.reseller_id = reseller_id
|
|
179
|
+
|
|
180
|
+
def execute(self, policies):
|
|
181
|
+
policies = [self._complete_data(policy) for policy in policies]
|
|
182
|
+
policies = self._create_db_records(policies)
|
|
183
|
+
policies = self._generate_signature_link(policies)
|
|
184
|
+
return policies
|
|
185
|
+
|
|
186
|
+
def _complete_data(self, policy):
|
|
187
|
+
lead = policy.get('data').get('lead')
|
|
188
|
+
# TODO: gerar o cadastro do lead no BD
|
|
189
|
+
|
|
190
|
+
broker = policy.get('data').get('reseller').get('broker')
|
|
191
|
+
# TODO: gerar o cadastro da unidade (corretora) no BD
|
|
192
|
+
# TODO: gerar o cadastro do vendedor padrao da unidade (corretora) no BD
|
|
193
|
+
|
|
194
|
+
# preencher os dados faltantes
|
|
195
|
+
policy.update({
|
|
196
|
+
'enterprise': {'id': self.enterprise_id},
|
|
197
|
+
'reseller': {'id': self.reseller_id, 'person': broker},
|
|
198
|
+
'lead': lead,
|
|
199
|
+
'status': 'quotation_completed',
|
|
200
|
+
'payment_method': 'bank_slip',
|
|
201
|
+
'multi': True,
|
|
202
|
+
'active': True,
|
|
203
|
+
'deleted': False
|
|
204
|
+
})
|
|
205
|
+
return policy
|
|
206
|
+
|
|
207
|
+
def _create_db_records(self, policies):
|
|
208
|
+
# TODO: insere os registros no BD
|
|
209
|
+
return policies
|
|
210
|
+
|
|
211
|
+
def _generate_signature_link(self, policies):
|
|
212
|
+
# TODO: gera o link de assinatura (via Paperless) para cada venda
|
|
213
|
+
# TODO: atualiza o status da venda p/ pending_signature
|
|
214
|
+
return policies
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Ponto de integracao na core-api (esboco):
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from transdesk.importer import TransdeskImporter
|
|
221
|
+
|
|
222
|
+
result = TransdeskImporter().import_policies(file_bytes)
|
|
223
|
+
if not result.is_valid:
|
|
224
|
+
... # retornar result.errors
|
|
225
|
+
|
|
226
|
+
policies = [p.to_dict() if hasattr(p, "to_dict") else p for p in result.policies]
|
|
227
|
+
# aplicar _complete_data / _create_db_records / _generate_signature_link aqui
|
|
228
|
+
```
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# transdesk-importer-python-sdk
|
|
2
|
+
|
|
3
|
+
SDK Python para leitura e transformacao de planilhas de apolices da **Transdesk**
|
|
4
|
+
no formato interno consumido pela `77seg-core-api`.
|
|
5
|
+
|
|
6
|
+
O SDK e **puro**: ele apenas **le, transforma e valida** a planilha, devolvendo
|
|
7
|
+
modelos tipados. Ele **nao** grava no banco, nao cadastra leads/corretoras e nao
|
|
8
|
+
gera link de assinatura -- essas responsabilidades ficam na core-api
|
|
9
|
+
(ver [Referencia de enrichment](#referencia-de-enrichment-core-api)).
|
|
10
|
+
|
|
11
|
+
## Instalacao
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install transdesk-importer-python-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requer Python >= 3.11.
|
|
18
|
+
|
|
19
|
+
## Uso
|
|
20
|
+
|
|
21
|
+
A entrada pode ser `bytes` (upload em memoria), um objeto file-like binario ou
|
|
22
|
+
um caminho de arquivo. O formato (`.xls`/`.xlsx`) e detectado automaticamente
|
|
23
|
+
por magic bytes -- nao depende da extensao.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from transdesk.importer import TransdeskImporter
|
|
27
|
+
|
|
28
|
+
# 1) a partir de bytes (ex.: upload de um endpoint)
|
|
29
|
+
file_bytes = request.files["file"].file.read()
|
|
30
|
+
result = TransdeskImporter().import_policies(file_bytes)
|
|
31
|
+
|
|
32
|
+
# 2) a partir de um caminho
|
|
33
|
+
result = TransdeskImporter().import_policies("apolices.xls")
|
|
34
|
+
|
|
35
|
+
if not result.is_valid:
|
|
36
|
+
# result.errors -> list[ValidationError]
|
|
37
|
+
for err in result.errors:
|
|
38
|
+
print(err)
|
|
39
|
+
else:
|
|
40
|
+
for policy in result.policies: # list[InternalPolicy]
|
|
41
|
+
print(policy.reference_id, len(policy.data.items))
|
|
42
|
+
|
|
43
|
+
# serializacao pronta para JSON
|
|
44
|
+
payload = result.to_dict() # dict
|
|
45
|
+
as_json = result.to_json() # str (ensure_ascii=False)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Retorno
|
|
49
|
+
|
|
50
|
+
`import_policies(file)` devolve um `ImportResult`:
|
|
51
|
+
|
|
52
|
+
| Campo | Tipo | Descricao |
|
|
53
|
+
| ---------- | ----------------------- | ------------------------------------------ |
|
|
54
|
+
| `policies` | `list[InternalPolicy]` | Apolices ja no formato interno |
|
|
55
|
+
| `errors` | `list[ValidationError]` | Divergencias encontradas na validacao |
|
|
56
|
+
| `is_valid` | `bool` | `True` quando `errors` esta vazio |
|
|
57
|
+
|
|
58
|
+
Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
|
|
59
|
+
|
|
60
|
+
### Principais modelos
|
|
61
|
+
|
|
62
|
+
- Saida (EN): `InternalPolicy`, `PolicyData`, `ResellerData`, `Broker`, `Lead`,
|
|
63
|
+
`CustomerInfo`, `Telephone`, `Mobile`, `Address`, `InternalItem`.
|
|
64
|
+
- Canonico (PT, em `InternalPolicy.original_data`): `CanonicalPolicy`,
|
|
65
|
+
`CanonicalItem`, `Subestipulante`, `Cliente`, `Endereco`, `DadosBancarios`,
|
|
66
|
+
`UnidadeVenda`, `Cobertura`, `Complemento`.
|
|
67
|
+
|
|
68
|
+
`InternalItem` tem um schema "achatado" e **consistente**: todos os campos de
|
|
69
|
+
cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
|
|
70
|
+
item (ex.: campos de vida num veiculo) vem como `null`.
|
|
71
|
+
|
|
72
|
+
### Validacao
|
|
73
|
+
|
|
74
|
+
O `ValidationError` cobre tres tipos:
|
|
75
|
+
|
|
76
|
+
- `policy_count` -- divergencia na contagem de apolices;
|
|
77
|
+
- `item_count` -- divergencia na contagem de itens de uma apolice;
|
|
78
|
+
- `premium` -- divergencia (apos arredondamento) entre o premio somado no
|
|
79
|
+
canonico e o premio somado no formato interno de um item.
|
|
80
|
+
|
|
81
|
+
## Arquitetura
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
planilha (bytes/file-like/path)
|
|
85
|
+
|
|
|
86
|
+
v
|
|
87
|
+
ExcelReader -> DataFrame
|
|
88
|
+
|
|
|
89
|
+
v
|
|
90
|
+
ApoliceBuilder -> list[CanonicalPolicy] (camada canonica, PT)
|
|
91
|
+
|
|
|
92
|
+
v
|
|
93
|
+
InternalMapper -> list[InternalPolicy] (camada interna, EN)
|
|
94
|
+
|
|
|
95
|
+
v
|
|
96
|
+
InternalPolicyValidator-> list[ValidationError]
|
|
97
|
+
|
|
|
98
|
+
v
|
|
99
|
+
ImportResult (policies + errors)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Desenvolvimento
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
python -m venv venv && source venv/bin/activate
|
|
106
|
+
pip install -e ".[dev]"
|
|
107
|
+
pytest
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Publicacao: criar uma tag `vX.Y.Z` dispara o workflow de publish no PyPI
|
|
111
|
+
(`.github/workflows/publish.yml`).
|
|
112
|
+
|
|
113
|
+
## Referencia de enrichment (core-api)
|
|
114
|
+
|
|
115
|
+
> Esta secao **nao** faz parte do SDK. Ela preserva, como referencia, a logica
|
|
116
|
+
> de "enrichment" e persistencia que **saiu** do importer e deve ser portada
|
|
117
|
+
> para a `77seg-core-api`.
|
|
118
|
+
|
|
119
|
+
Apos obter as `InternalPolicy` do SDK, a core-api deve:
|
|
120
|
+
|
|
121
|
+
1. **Completar os dados** de cada apolice (cadastrar lead e unidade/corretora,
|
|
122
|
+
injetar `enterprise_id`/`reseller_id` e os defaults da venda);
|
|
123
|
+
2. **Persistir** os registros das vendas no banco;
|
|
124
|
+
3. **Gerar o link de assinatura** (Paperless) e atualizar o status para
|
|
125
|
+
`pending_signature`.
|
|
126
|
+
|
|
127
|
+
Codigo original (removido do SDK), que serve de base para a implementacao na
|
|
128
|
+
core-api:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
class InternalImportPipeline:
|
|
132
|
+
|
|
133
|
+
def __init__(self, enterprise_id, reseller_id):
|
|
134
|
+
self.enterprise_id = enterprise_id
|
|
135
|
+
self.reseller_id = reseller_id
|
|
136
|
+
|
|
137
|
+
def execute(self, policies):
|
|
138
|
+
policies = [self._complete_data(policy) for policy in policies]
|
|
139
|
+
policies = self._create_db_records(policies)
|
|
140
|
+
policies = self._generate_signature_link(policies)
|
|
141
|
+
return policies
|
|
142
|
+
|
|
143
|
+
def _complete_data(self, policy):
|
|
144
|
+
lead = policy.get('data').get('lead')
|
|
145
|
+
# TODO: gerar o cadastro do lead no BD
|
|
146
|
+
|
|
147
|
+
broker = policy.get('data').get('reseller').get('broker')
|
|
148
|
+
# TODO: gerar o cadastro da unidade (corretora) no BD
|
|
149
|
+
# TODO: gerar o cadastro do vendedor padrao da unidade (corretora) no BD
|
|
150
|
+
|
|
151
|
+
# preencher os dados faltantes
|
|
152
|
+
policy.update({
|
|
153
|
+
'enterprise': {'id': self.enterprise_id},
|
|
154
|
+
'reseller': {'id': self.reseller_id, 'person': broker},
|
|
155
|
+
'lead': lead,
|
|
156
|
+
'status': 'quotation_completed',
|
|
157
|
+
'payment_method': 'bank_slip',
|
|
158
|
+
'multi': True,
|
|
159
|
+
'active': True,
|
|
160
|
+
'deleted': False
|
|
161
|
+
})
|
|
162
|
+
return policy
|
|
163
|
+
|
|
164
|
+
def _create_db_records(self, policies):
|
|
165
|
+
# TODO: insere os registros no BD
|
|
166
|
+
return policies
|
|
167
|
+
|
|
168
|
+
def _generate_signature_link(self, policies):
|
|
169
|
+
# TODO: gera o link de assinatura (via Paperless) para cada venda
|
|
170
|
+
# TODO: atualiza o status da venda p/ pending_signature
|
|
171
|
+
return policies
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Ponto de integracao na core-api (esboco):
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from transdesk.importer import TransdeskImporter
|
|
178
|
+
|
|
179
|
+
result = TransdeskImporter().import_policies(file_bytes)
|
|
180
|
+
if not result.is_valid:
|
|
181
|
+
... # retornar result.errors
|
|
182
|
+
|
|
183
|
+
policies = [p.to_dict() if hasattr(p, "to_dict") else p for p in result.policies]
|
|
184
|
+
# aplicar _complete_data / _create_db_records / _generate_signature_link aqui
|
|
185
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "transdesk-importer-python-sdk"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "FMConsult", email = "contato@fmconsult.com.br" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["sdk", "transdesk", "importer", "apolices", "77seg"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pandas>=2.0",
|
|
24
|
+
"openpyxl>=3.1",
|
|
25
|
+
"xlrd>=2.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=7.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
"Homepage" = "https://github.com/fmconsult/77seg-transdesk-importer"
|
|
35
|
+
"Bug Tracker" = "https://github.com/fmconsult/77seg-transdesk-importer/issues"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
namespaces = true
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-dir]
|
|
42
|
+
"" = "src"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""SDK de importacao de planilhas Transdesk.
|
|
2
|
+
|
|
3
|
+
Uso basico::
|
|
4
|
+
|
|
5
|
+
from transdesk.importer import TransdeskImporter
|
|
6
|
+
|
|
7
|
+
result = TransdeskImporter().import_policies(file_bytes)
|
|
8
|
+
result.policies # list[InternalPolicy]
|
|
9
|
+
result.errors # list[ValidationError]
|
|
10
|
+
result.to_dict() # dict pronto para JSON
|
|
11
|
+
"""
|
|
12
|
+
from transdesk.importer.client import TransdeskImporter
|
|
13
|
+
from transdesk.importer.models import (
|
|
14
|
+
Address,
|
|
15
|
+
Broker,
|
|
16
|
+
CanonicalItem,
|
|
17
|
+
CanonicalPolicy,
|
|
18
|
+
Cliente,
|
|
19
|
+
Cobertura,
|
|
20
|
+
Complemento,
|
|
21
|
+
CustomerInfo,
|
|
22
|
+
DadosBancarios,
|
|
23
|
+
Endereco,
|
|
24
|
+
ImportResult,
|
|
25
|
+
InternalItem,
|
|
26
|
+
InternalPolicy,
|
|
27
|
+
Lead,
|
|
28
|
+
Mobile,
|
|
29
|
+
PolicyData,
|
|
30
|
+
ReboqueRisco,
|
|
31
|
+
ResellerData,
|
|
32
|
+
Subestipulante,
|
|
33
|
+
Telephone,
|
|
34
|
+
UnidadeVenda,
|
|
35
|
+
ValidationError,
|
|
36
|
+
VeiculoRisco,
|
|
37
|
+
VidaRisco,
|
|
38
|
+
)
|
|
39
|
+
from transdesk.importer.readers.excel_reader import UnsupportedSpreadsheetError
|
|
40
|
+
|
|
41
|
+
__version__ = "1.0.0"
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"TransdeskImporter",
|
|
45
|
+
"UnsupportedSpreadsheetError",
|
|
46
|
+
"ImportResult",
|
|
47
|
+
"ValidationError",
|
|
48
|
+
"InternalPolicy",
|
|
49
|
+
"PolicyData",
|
|
50
|
+
"ResellerData",
|
|
51
|
+
"Broker",
|
|
52
|
+
"Lead",
|
|
53
|
+
"CustomerInfo",
|
|
54
|
+
"Telephone",
|
|
55
|
+
"Mobile",
|
|
56
|
+
"Address",
|
|
57
|
+
"InternalItem",
|
|
58
|
+
"CanonicalPolicy",
|
|
59
|
+
"CanonicalItem",
|
|
60
|
+
"Subestipulante",
|
|
61
|
+
"Cliente",
|
|
62
|
+
"Endereco",
|
|
63
|
+
"DadosBancarios",
|
|
64
|
+
"UnidadeVenda",
|
|
65
|
+
"Cobertura",
|
|
66
|
+
"Complemento",
|
|
67
|
+
"VeiculoRisco",
|
|
68
|
+
"ReboqueRisco",
|
|
69
|
+
"VidaRisco",
|
|
70
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Fachada publica do SDK.
|
|
2
|
+
|
|
3
|
+
Recebe a planilha (bytes / file-like / caminho), executa o pipeline
|
|
4
|
+
canonical -> internal -> validate e devolve um ``ImportResult`` tipado.
|
|
5
|
+
"""
|
|
6
|
+
from transdesk.importer.models.internal import ImportResult
|
|
7
|
+
from transdesk.importer.readers.excel_reader import ExcelReader
|
|
8
|
+
from transdesk.importer.transformers.canonical.apolice_builder import ApoliceBuilder
|
|
9
|
+
from transdesk.importer.transformers.internal.internal_mapper import InternalMapper
|
|
10
|
+
from transdesk.importer.validators.internal_policy_validator import InternalPolicyValidator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TransdeskImporter:
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.reader = ExcelReader()
|
|
17
|
+
self.canonical_builder = ApoliceBuilder()
|
|
18
|
+
self.mapper = InternalMapper()
|
|
19
|
+
self.validator = InternalPolicyValidator()
|
|
20
|
+
|
|
21
|
+
def import_policies(self, file) -> ImportResult:
|
|
22
|
+
"""Le a planilha e devolve as apolices no formato interno.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
file: ``bytes``/``bytearray``, objeto file-like binario ou caminho
|
|
26
|
+
de arquivo (``str``/``os.PathLike``).
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
ImportResult: ``policies`` (list[InternalPolicy]) e ``errors``
|
|
30
|
+
(list[ValidationError]).
|
|
31
|
+
"""
|
|
32
|
+
df = self.reader.read(file)
|
|
33
|
+
|
|
34
|
+
canonical_policies = self.canonical_builder.build(df)
|
|
35
|
+
internal_policies = [
|
|
36
|
+
self.mapper.map(policy)
|
|
37
|
+
for policy in canonical_policies
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
errors = self.validator.validate(canonical_policies, internal_policies)
|
|
41
|
+
|
|
42
|
+
return ImportResult(policies=internal_policies, errors=errors)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Utilitario de desenvolvimento/debug para gravar resultados em JSON.
|
|
2
|
+
|
|
3
|
+
Nao faz parte do caminho principal do SDK (que retorna models tipados), mas
|
|
4
|
+
ajuda a inspecionar saidas durante o desenvolvimento. Lida com dataclasses,
|
|
5
|
+
listas e ``ImportResult``.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
from dataclasses import asdict, is_dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _default(obj):
|
|
12
|
+
if is_dataclass(obj) and not isinstance(obj, type):
|
|
13
|
+
return asdict(obj)
|
|
14
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JsonExporter:
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def export(data, output_file):
|
|
21
|
+
if hasattr(data, "to_dict"):
|
|
22
|
+
data = data.to_dict()
|
|
23
|
+
|
|
24
|
+
with open(output_file, "w", encoding="utf8") as f:
|
|
25
|
+
json.dump(data, f, ensure_ascii=False, indent=4, default=_default)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from .canonical import (
|
|
2
|
+
CanonicalItem,
|
|
3
|
+
CanonicalPolicy,
|
|
4
|
+
Cliente,
|
|
5
|
+
Cobertura,
|
|
6
|
+
Complemento,
|
|
7
|
+
DadosBancarios,
|
|
8
|
+
DadosRisco,
|
|
9
|
+
Endereco,
|
|
10
|
+
ReboqueRisco,
|
|
11
|
+
Subestipulante,
|
|
12
|
+
UnidadeVenda,
|
|
13
|
+
VeiculoRisco,
|
|
14
|
+
VidaRisco,
|
|
15
|
+
)
|
|
16
|
+
from .internal import (
|
|
17
|
+
Address,
|
|
18
|
+
Broker,
|
|
19
|
+
CustomerInfo,
|
|
20
|
+
ImportResult,
|
|
21
|
+
InternalItem,
|
|
22
|
+
InternalPolicy,
|
|
23
|
+
Lead,
|
|
24
|
+
Mobile,
|
|
25
|
+
PolicyData,
|
|
26
|
+
ResellerData,
|
|
27
|
+
Telephone,
|
|
28
|
+
ValidationError,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
# canonical
|
|
33
|
+
"CanonicalItem",
|
|
34
|
+
"CanonicalPolicy",
|
|
35
|
+
"Cliente",
|
|
36
|
+
"Cobertura",
|
|
37
|
+
"Complemento",
|
|
38
|
+
"DadosBancarios",
|
|
39
|
+
"DadosRisco",
|
|
40
|
+
"Endereco",
|
|
41
|
+
"ReboqueRisco",
|
|
42
|
+
"Subestipulante",
|
|
43
|
+
"UnidadeVenda",
|
|
44
|
+
"VeiculoRisco",
|
|
45
|
+
"VidaRisco",
|
|
46
|
+
# internal
|
|
47
|
+
"Address",
|
|
48
|
+
"Broker",
|
|
49
|
+
"CustomerInfo",
|
|
50
|
+
"ImportResult",
|
|
51
|
+
"InternalItem",
|
|
52
|
+
"InternalPolicy",
|
|
53
|
+
"Lead",
|
|
54
|
+
"Mobile",
|
|
55
|
+
"PolicyData",
|
|
56
|
+
"ResellerData",
|
|
57
|
+
"Telephone",
|
|
58
|
+
"ValidationError",
|
|
59
|
+
]
|