transdesk-importer-python-sdk 1.0.0__tar.gz → 1.2.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.
Files changed (38) hide show
  1. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/PKG-INFO +16 -1
  2. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/README.md +15 -0
  3. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/pyproject.toml +1 -1
  4. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/models/canonical.py +18 -0
  5. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/models/internal.py +23 -4
  6. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/shared/data_normalizer.py +21 -1
  7. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/canonical/apolice_builder.py +33 -4
  8. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/internal/coverage_mapper.py +18 -12
  9. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/internal/internal_mapper.py +78 -5
  10. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/internal/rcf_parser.py +1 -1
  11. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk_importer_python_sdk.egg-info/PKG-INFO +16 -1
  12. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk_importer_python_sdk.egg-info/SOURCES.txt +3 -1
  13. transdesk_importer_python_sdk-1.2.0/tests/test_data_normalizer.py +26 -0
  14. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/tests/test_importer.py +4 -1
  15. transdesk_importer_python_sdk-1.2.0/tests/test_vehicle_details.py +181 -0
  16. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/LICENSE +0 -0
  17. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/setup.cfg +0 -0
  18. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/__init__.py +0 -0
  19. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/client.py +0 -0
  20. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/exporters/__init__.py +0 -0
  21. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/exporters/json_exporter.py +0 -0
  22. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/models/__init__.py +0 -0
  23. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/readers/__init__.py +0 -0
  24. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/readers/excel_reader.py +0 -0
  25. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/shared/__init__.py +0 -0
  26. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/__init__.py +0 -0
  27. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/canonical/__init__.py +0 -0
  28. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/canonical/cobertura_builder.py +0 -0
  29. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/canonical/item_classifier.py +0 -0
  30. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/internal/__init__.py +0 -0
  31. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/transformers/internal/towing_parser.py +0 -0
  32. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/validators/__init__.py +0 -0
  33. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk/importer/validators/internal_policy_validator.py +0 -0
  34. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk_importer_python_sdk.egg-info/dependency_links.txt +0 -0
  35. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk_importer_python_sdk.egg-info/requires.txt +0 -0
  36. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/src/transdesk_importer_python_sdk.egg-info/top_level.txt +0 -0
  37. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/tests/test_excel_reader.py +0 -0
  38. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.2.0}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transdesk-importer-python-sdk
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg
5
5
  Author-email: FMConsult <contato@fmconsult.com.br>
6
6
  License: MIT License
@@ -112,6 +112,21 @@ Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
112
112
  cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
113
113
  item (ex.: campos de vida num veiculo) vem como `null`.
114
114
 
115
+ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
116
+
117
+ | Coluna planilha | Campo `InternalItem` | Observacao |
118
+ | --------------- | -------------------- | ---------- |
119
+ | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
120
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
121
+ | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
122
+ | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
123
+ | `RENAVAM` | `renavam` | |
124
+ | `COR` | `color` | |
125
+ | `ANO` | `model_year`, `manufacture_year` | Mesmo valor nos dois campos |
126
+ | `EIXOS` | `axles` | |
127
+ | `CAMBIO` | `transmission` | |
128
+ | `ALIENADO` | `is_alienated` | `N/A` ou vazio = `false` |
129
+
115
130
  ### Validacao
116
131
 
117
132
  O `ValidationError` cobre tres tipos:
@@ -69,6 +69,21 @@ Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
69
69
  cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
70
70
  item (ex.: campos de vida num veiculo) vem como `null`.
71
71
 
72
+ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
73
+
74
+ | Coluna planilha | Campo `InternalItem` | Observacao |
75
+ | --------------- | -------------------- | ---------- |
76
+ | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
77
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
78
+ | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
79
+ | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
80
+ | `RENAVAM` | `renavam` | |
81
+ | `COR` | `color` | |
82
+ | `ANO` | `model_year`, `manufacture_year` | Mesmo valor nos dois campos |
83
+ | `EIXOS` | `axles` | |
84
+ | `CAMBIO` | `transmission` | |
85
+ | `ALIENADO` | `is_alienated` | `N/A` ou vazio = `false` |
86
+
72
87
  ### Validacao
73
88
 
74
89
  O `ValidationError` cobre tres tipos:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "transdesk-importer-python-sdk"
7
- version = "1.0.0"
7
+ version = "1.2.0"
8
8
  description = "SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -36,6 +36,16 @@ class VeiculoRisco:
36
36
  placa: Optional[str] = None
37
37
  chassi: Optional[str] = None
38
38
  codigo_fipe: Optional[str] = None
39
+ empresa_rastreamento: Optional[str] = None
40
+ categoria: Optional[str] = None
41
+ modelo: Optional[str] = None
42
+ renavam: Optional[str] = None
43
+ marca: Optional[str] = None
44
+ cor: Optional[str] = None
45
+ ano: Optional[str] = None
46
+ eixos: Optional[str] = None
47
+ cambio: Optional[str] = None
48
+ alienado: Optional[str] = None
39
49
 
40
50
 
41
51
  @dataclass
@@ -44,6 +54,14 @@ class ReboqueRisco:
44
54
  chassi: Optional[str] = None
45
55
  marca: Optional[str] = None
46
56
  modelo: Optional[str] = None
57
+ empresa_rastreamento: Optional[str] = None
58
+ categoria: Optional[str] = None
59
+ renavam: Optional[str] = None
60
+ cor: Optional[str] = None
61
+ ano: Optional[str] = None
62
+ eixos: Optional[str] = None
63
+ cambio: Optional[str] = None
64
+ alienado: Optional[str] = None
47
65
 
48
66
 
49
67
  @dataclass
@@ -23,22 +23,25 @@ class Broker:
23
23
 
24
24
  @dataclass
25
25
  class CustomerInfo:
26
+ cpf: Optional[str] = None
26
27
  cnpj: Optional[str] = None
27
28
  principal_name: Optional[str] = None
28
29
  secondary_name: Optional[str] = None
29
30
  email_address: Optional[str] = None
31
+ enrollment_number: Optional[str] = None
32
+ custom_fields: Optional[Dict[str, Any]] = None
30
33
 
31
34
 
32
35
  @dataclass
33
36
  class Telephone:
34
- telphone_type_code: str
37
+ type_code: str
35
38
  ddd: str
36
39
  number: str
37
40
 
38
41
 
39
42
  @dataclass
40
43
  class Mobile:
41
- telphone_type_code: str
44
+ type_code: str
42
45
  ddd: str
43
46
  number: str
44
47
  allow_sms: bool = True
@@ -85,7 +88,7 @@ class InternalItem:
85
88
 
86
89
  # RCF
87
90
  has_rcf: bool = False
88
- property_damage_value: float = 0
91
+ material_damage_value: float = 0
89
92
  property_damage_premium: float = 0
90
93
  bodily_injury_value: float = 0
91
94
  bodily_injury_premium: float = 0
@@ -102,18 +105,24 @@ class InternalItem:
102
105
  towing_premium: float = 0
103
106
 
104
107
  # Vidros
105
- glass_coverage_type: Optional[str] = None
108
+ glass_coverage_level: Optional[str] = None
106
109
  glass_premium: float = 0
107
110
 
108
111
  # Rastreador
109
112
  has_vehicle_tracker: bool = False
110
113
  vehicle_tracker_premium: float = 0
114
+ vehicle_tracker_company: Optional[str] = None
111
115
 
112
116
  # Juridica
113
117
  has_legal_assistance: bool = False
114
118
  legal_assistance_value: float = 0
115
119
  legal_assistance_premium: float = 0
116
120
 
121
+ # Suporte emergencial monitorado
122
+ has_monitored_emergency_support: bool = False
123
+ monitored_emergency_support_value: float = 0
124
+ monitored_emergency_support_premium: float = 0
125
+
117
126
  # Combo
118
127
  group_name: Optional[str] = None
119
128
 
@@ -124,6 +133,16 @@ class InternalItem:
124
133
  body_description: Optional[str] = None
125
134
  brand: Optional[str] = None
126
135
  model: Optional[str] = None
136
+ brand_name: Optional[str] = None
137
+ model_description: Optional[str] = None
138
+ vehicle_classification: Optional[int] = None
139
+ renavam: Optional[str] = None
140
+ color: Optional[str] = None
141
+ model_year: Optional[int] = None
142
+ manufacture_year: Optional[int] = None
143
+ axles: Optional[int] = None
144
+ transmission: Optional[str] = None
145
+ is_alienated: bool = False
127
146
 
128
147
  # Vida
129
148
  name: Optional[str] = None
@@ -41,7 +41,27 @@ class DataNormalizer:
41
41
 
42
42
  @staticmethod
43
43
  def to_int(value):
44
+ if value in (None, ""):
45
+ return None
46
+
44
47
  try:
45
- return int(value)
48
+ return int(str(value).strip())
46
49
  except Exception:
47
50
  return None
51
+
52
+ @staticmethod
53
+ def to_bool(value, na_values=("N/A", "n/a", "")):
54
+ if value is None:
55
+ return False
56
+
57
+ normalized = str(value).strip()
58
+ if normalized in na_values:
59
+ return False
60
+
61
+ upper = normalized.upper()
62
+ if upper in ("SIM", "S", "TRUE", "1", "YES", "Y"):
63
+ return True
64
+ if upper in ("NÃO", "NAO", "N", "FALSE", "0", "NO"):
65
+ return False
66
+
67
+ return False
@@ -188,6 +188,7 @@ class ApoliceBuilder:
188
188
  chassi=row.get("CHASSI"),
189
189
  marca=row.get("MARCA_REBOQUE"),
190
190
  modelo=row.get("MODELO_REBOQUE"),
191
+ **self._read_vehicle_detail_fields(rows, include_brand_model=False),
191
192
  ),
192
193
  coberturas=CoberturaBuilder(rows).build(),
193
194
  )
@@ -205,6 +206,7 @@ class ApoliceBuilder:
205
206
  placa=row.get("PLACA_VEICULO"),
206
207
  chassi=row.get("CHASSI"),
207
208
  codigo_fipe=row.get("CODE_FIPE"),
209
+ **self._read_vehicle_detail_fields(rows, include_brand_model=True),
208
210
  ),
209
211
  complementos=self.build_complementos(rows),
210
212
  coberturas=CoberturaBuilder(rows).build(),
@@ -214,6 +216,36 @@ class ApoliceBuilder:
214
216
 
215
217
  return item
216
218
 
219
+ def _read_vehicle_detail_fields(self, rows, include_brand_model=False):
220
+ column_map = {
221
+ "empresa_rastreamento": "EMPRESA_RASTREAMENTO",
222
+ "categoria": "CATEGORIA",
223
+ "renavam": "RENAVAM",
224
+ "cor": "COR",
225
+ "ano": "ANO",
226
+ "eixos": "EIXOS",
227
+ "cambio": "CAMBIO",
228
+ "alienado": "ALIENADO",
229
+ }
230
+
231
+ if include_brand_model:
232
+ column_map["marca"] = "MARCA"
233
+ column_map["modelo"] = "MODELO"
234
+
235
+ fields = {}
236
+ for field_name, column_name in column_map.items():
237
+ fields[field_name] = self._first_non_empty(rows, column_name)
238
+
239
+ return fields
240
+
241
+ @staticmethod
242
+ def _first_non_empty(rows, column_name):
243
+ for row in rows:
244
+ value = row.get(column_name)
245
+ if value is not None and str(value).strip() != "":
246
+ return value
247
+ return None
248
+
217
249
  # ---------------------------------------------------------
218
250
  # COMPLEMENTOS
219
251
  # ---------------------------------------------------------
@@ -246,7 +278,6 @@ class ApoliceBuilder:
246
278
 
247
279
  def merge_complement_coverages(self, item):
248
280
  casco_extra = 0
249
- reparo_extra = 0
250
281
 
251
282
  novas = []
252
283
 
@@ -258,7 +289,7 @@ class ApoliceBuilder:
258
289
  continue
259
290
 
260
291
  if nome == "Assistência a Reparos (COMPLEMENTO)":
261
- reparo_extra += (c.valor_mensalidade or 0)
292
+ casco_extra += (c.valor_mensalidade or 0)
262
293
  continue
263
294
 
264
295
  novas.append(c)
@@ -266,7 +297,5 @@ class ApoliceBuilder:
266
297
  for c in novas:
267
298
  if c.nome == "Casco PT (VEÍCULO)":
268
299
  c.valor_mensalidade = ((c.valor_mensalidade or 0) + casco_extra)
269
- elif c.nome == "Assistência a Reparos (VEÍCULO)":
270
- c.valor_mensalidade = ((c.valor_mensalidade or 0) + reparo_extra)
271
300
 
272
301
  item.coberturas = novas
@@ -33,11 +33,9 @@ class CoverageMapper:
33
33
  elif name in (
34
34
  "Assistência a Reparos (VEÍCULO)",
35
35
  "Assistência a Reparos (R/SR)",
36
+ "Assistência a Reparos (COMPLEMENTO)",
36
37
  ):
37
- self._apply_repair(item, coverage)
38
-
39
- elif name == "Assistência a Reparos (COMPLEMENTO)":
40
- self._apply_body(item, coverage)
38
+ self._apply_casco_premium_only(item, coverage)
41
39
 
42
40
  elif name == "RCF":
43
41
  self._apply_rcf(item, coverage)
@@ -57,10 +55,14 @@ class CoverageMapper:
57
55
  elif name in (
58
56
  "Rastreador p/ Veículo",
59
57
  "Rastreador p/ Reboque",
60
- "SUPORTE EMERGENCIAL MONITORADO",
58
+ "Rastreamento",
59
+ "Rastreamento Reboque/Semirreboque",
61
60
  ):
62
61
  self._apply_tracker(item, coverage)
63
62
 
63
+ elif name == "SUPORTE EMERGENCIAL MONITORADO":
64
+ self._apply_monitored_emergency_support(item, coverage)
65
+
64
66
  elif name == "Assistência Jurídica":
65
67
  self._apply_legal_assistance(item, coverage)
66
68
 
@@ -69,10 +71,9 @@ class CoverageMapper:
69
71
  item.hull_value += (coverage.importancia_segurada or 0)
70
72
  item.hull_premium += (coverage.valor_mensalidade or 0)
71
73
 
72
- def _apply_repair(self, item, coverage):
73
- item.has_repair_assistance = True
74
- item.repair_assistance_value += (coverage.importancia_segurada or 0)
75
- item.repair_assistance_premium += (coverage.valor_mensalidade or 0)
74
+ def _apply_casco_premium_only(self, item, coverage):
75
+ item.has_casco_cir = True
76
+ item.hull_premium += (coverage.valor_mensalidade or 0)
76
77
 
77
78
  def _apply_body(self, item, coverage):
78
79
  item.body_value += (coverage.importancia_segurada or 0)
@@ -88,12 +89,17 @@ class CoverageMapper:
88
89
  item.legal_assistance_value = 1
89
90
  item.legal_assistance_premium += (coverage.valor_mensalidade or 0)
90
91
 
92
+ def _apply_monitored_emergency_support(self, item, coverage):
93
+ item.has_monitored_emergency_support = True
94
+ item.monitored_emergency_support_value = 1
95
+ item.monitored_emergency_support_premium += (coverage.valor_mensalidade or 0)
96
+
91
97
  def _apply_tracker(self, item, coverage):
92
98
  item.has_vehicle_tracker = True
93
99
  item.vehicle_tracker_premium += (coverage.valor_mensalidade or 0)
94
100
 
95
101
  def _apply_glass(self, item, coverage):
96
- item.glass_coverage_type = "completo"
102
+ item.glass_coverage_level = "completo"
97
103
  item.glass_premium += (coverage.valor_mensalidade or 0)
98
104
 
99
105
  def _apply_rcf(self, item, coverage):
@@ -104,13 +110,13 @@ class CoverageMapper:
104
110
  premiums = self._allocate_premium(
105
111
  coverage.valor_mensalidade or 0,
106
112
  {
107
- "property_damage": parsed["property_damage_value"],
113
+ "property_damage": parsed["material_damage_value"],
108
114
  "bodily_injury": parsed["bodily_injury_value"],
109
115
  "moral_damage": parsed["moral_damage_value"],
110
116
  },
111
117
  )
112
118
 
113
- item.property_damage_value += (parsed["property_damage_value"])
119
+ item.material_damage_value += (parsed["material_damage_value"])
114
120
  item.property_damage_premium += (premiums["property_damage"])
115
121
 
116
122
  item.bodily_injury_value += (parsed["bodily_injury_value"])
@@ -10,6 +10,7 @@ from transdesk.importer.models.internal import (
10
10
  ResellerData,
11
11
  Telephone,
12
12
  )
13
+ from transdesk.importer.shared.data_normalizer import DataNormalizer
13
14
  from transdesk.importer.transformers.internal.coverage_mapper import CoverageMapper
14
15
 
15
16
 
@@ -44,19 +45,61 @@ class InternalMapper:
44
45
  customer = policy.cliente
45
46
 
46
47
  email = next((e for e in customer.emails if e), None)
48
+ cpf, cnpj = self._route_document(customer.documento, customer.tipo_pessoa)
47
49
 
48
50
  return Lead(
49
51
  customer_info=CustomerInfo(
50
- cnpj=customer.documento,
51
- principal_name=(customer.razao_social or customer.nome_fantasia),
52
- secondary_name="",
52
+ cpf=cpf,
53
+ cnpj=cnpj,
54
+ principal_name=customer.razao_social or customer.nome_fantasia,
55
+ secondary_name=customer.nome_fantasia or "",
53
56
  email_address=email,
57
+ enrollment_number=customer.matricula,
58
+ custom_fields=self._build_bank_custom_fields(customer),
54
59
  ),
55
60
  telphone_info=self._build_phone(customer),
56
61
  celphone_info=self._build_mobile(customer),
57
62
  address_info=self._build_address(customer),
58
63
  )
59
64
 
65
+ def _build_bank_custom_fields(self, customer):
66
+ bank = customer.dados_bancarios
67
+
68
+ if bank is None:
69
+ return None
70
+
71
+ bank_info = {
72
+ 'bank_code': bank.banco,
73
+ 'agency': bank.agencia,
74
+ 'account': bank.conta,
75
+ 'pix_key': bank.chave_pix,
76
+ 'holder_name': bank.titular,
77
+ 'holder_document': bank.documento_titular,
78
+ }
79
+
80
+ if not any(bank_info.values()):
81
+ return None
82
+
83
+ return {'bank_info': bank_info}
84
+
85
+ def _route_document(self, documento, tipo_pessoa=None):
86
+ digits = "".join(filter(str.isdigit, str(documento or "")))
87
+
88
+ if not digits:
89
+ return None, None
90
+
91
+ tipo = str(tipo_pessoa or "").strip().upper()
92
+
93
+ if tipo in ("F", "PF", "FISICA", "FÍSICA"):
94
+ return digits, None
95
+ if tipo in ("J", "PJ", "JURIDICA", "JURÍDICA"):
96
+ return None, digits
97
+
98
+ if len(digits) <= 11:
99
+ return digits.zfill(11), None
100
+
101
+ return None, digits.zfill(14)
102
+
60
103
  def build_items(self, policy):
61
104
  return [
62
105
  self.map_item(item, index)
@@ -77,7 +120,7 @@ class InternalMapper:
77
120
 
78
121
  if not numero.startswith("9"):
79
122
  return Telephone(
80
- telphone_type_code="03",
123
+ type_code="03",
81
124
  ddd=digits[:2],
82
125
  number=numero,
83
126
  )
@@ -98,7 +141,7 @@ class InternalMapper:
98
141
 
99
142
  if numero.startswith("9"):
100
143
  return Mobile(
101
- telphone_type_code="02",
144
+ type_code="02",
102
145
  ddd=digits[:2],
103
146
  number=numero,
104
147
  allow_sms=True,
@@ -154,6 +197,36 @@ class InternalMapper:
154
197
  if item.tipo_item == "reboque":
155
198
  internal_item.brand = getattr(risco, "marca", None)
156
199
  internal_item.model = getattr(risco, "modelo", None)
200
+ internal_item.brand_name = internal_item.brand
201
+ internal_item.model_description = internal_item.model
202
+ elif item.tipo_item == "veiculo":
203
+ marca = getattr(risco, "marca", None)
204
+ modelo = getattr(risco, "modelo", None)
205
+ internal_item.brand = marca
206
+ internal_item.model = modelo
207
+ internal_item.brand_name = marca
208
+ internal_item.model_description = modelo
209
+
210
+ internal_item.vehicle_classification = DataNormalizer.to_int(
211
+ getattr(risco, "categoria", None)
212
+ )
213
+ internal_item.renavam = DataNormalizer.clean_doc(
214
+ getattr(risco, "renavam", None)
215
+ )
216
+ internal_item.color = getattr(risco, "cor", None) or None
217
+ year = DataNormalizer.to_int(getattr(risco, "ano", None))
218
+ internal_item.model_year = year
219
+ internal_item.manufacture_year = year
220
+ internal_item.axles = DataNormalizer.to_int(getattr(risco, "eixos", None))
221
+ internal_item.transmission = getattr(risco, "cambio", None) or None
222
+ internal_item.is_alienated = DataNormalizer.to_bool(
223
+ getattr(risco, "alienado", None)
224
+ )
225
+
226
+ if internal_item.has_vehicle_tracker:
227
+ internal_item.vehicle_tracker_company = (
228
+ getattr(risco, "empresa_rastreamento", None) or None
229
+ )
157
230
 
158
231
  def _build_body_description(self, item):
159
232
  parts = []
@@ -10,7 +10,7 @@ class RCFParser:
10
10
  dmo = self.extract_dmo(produto)
11
11
 
12
12
  return {
13
- "property_damage_value": dma_dc,
13
+ "material_damage_value": dma_dc,
14
14
  "bodily_injury_value": dma_dc,
15
15
  "moral_damage_value": dmo,
16
16
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transdesk-importer-python-sdk
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg
5
5
  Author-email: FMConsult <contato@fmconsult.com.br>
6
6
  License: MIT License
@@ -112,6 +112,21 @@ Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
112
112
  cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
113
113
  item (ex.: campos de vida num veiculo) vem como `null`.
114
114
 
115
+ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
116
+
117
+ | Coluna planilha | Campo `InternalItem` | Observacao |
118
+ | --------------- | -------------------- | ---------- |
119
+ | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
120
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
121
+ | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
122
+ | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
123
+ | `RENAVAM` | `renavam` | |
124
+ | `COR` | `color` | |
125
+ | `ANO` | `model_year`, `manufacture_year` | Mesmo valor nos dois campos |
126
+ | `EIXOS` | `axles` | |
127
+ | `CAMBIO` | `transmission` | |
128
+ | `ALIENADO` | `is_alienated` | `N/A` ou vazio = `false` |
129
+
115
130
  ### Validacao
116
131
 
117
132
  O `ValidationError` cobre tres tipos:
@@ -29,6 +29,8 @@ src/transdesk_importer_python_sdk.egg-info/SOURCES.txt
29
29
  src/transdesk_importer_python_sdk.egg-info/dependency_links.txt
30
30
  src/transdesk_importer_python_sdk.egg-info/requires.txt
31
31
  src/transdesk_importer_python_sdk.egg-info/top_level.txt
32
+ tests/test_data_normalizer.py
32
33
  tests/test_excel_reader.py
33
34
  tests/test_importer.py
34
- tests/test_validator.py
35
+ tests/test_validator.py
36
+ tests/test_vehicle_details.py
@@ -0,0 +1,26 @@
1
+ import pytest
2
+
3
+ from transdesk.importer.shared.data_normalizer import DataNormalizer
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ "value,expected",
8
+ [
9
+ ("N/A", False),
10
+ ("n/a", False),
11
+ ("", False),
12
+ (None, False),
13
+ ("SIM", True),
14
+ ("sim", True),
15
+ ("S", True),
16
+ ("1", True),
17
+ ("true", True),
18
+ ("NÃO", False),
19
+ ("NAO", False),
20
+ ("N", False),
21
+ ("0", False),
22
+ ("false", False),
23
+ ],
24
+ )
25
+ def test_to_bool(value, expected):
26
+ assert DataNormalizer.to_bool(value) is expected
@@ -35,11 +35,14 @@ def test_policy_structure(apolices_bytes):
35
35
  assert broker.principal_name == "TRANSDESK UNIDADE CURITIBA"
36
36
 
37
37
  assert policy.data.lead.customer_info.principal_name == "ALM TRANSPORTES E LOGISTICA LTDA"
38
+ assert policy.data.lead.customer_info.cnpj
38
39
 
39
40
  item = policy.data.items[0]
40
41
  assert item.item_number == 1
41
42
  assert item.item_type == "veiculo"
42
- assert item.hull_premium == 397.88
43
+ assert item.hull_premium == 687.12
44
+ assert item.repair_assistance_premium == 0
45
+ assert item.has_repair_assistance is False
43
46
  assert item.group_name == "COMBO II"
44
47
 
45
48
 
@@ -0,0 +1,181 @@
1
+ import pandas as pd
2
+
3
+ from transdesk.importer.transformers.canonical.apolice_builder import ApoliceBuilder
4
+ from transdesk.importer.transformers.internal.internal_mapper import InternalMapper
5
+
6
+
7
+ def _base_row(**overrides):
8
+ row = {
9
+ "COD_AGRUPADOR_APOLICE": "99",
10
+ "DIA_VCTO": "10",
11
+ "ID_UNITY": "1",
12
+ "SUB_ESTIPULANTE_CNPJ": "12345678000199",
13
+ "SUB_ESTIPULANTE_FANTASIA": "CORRETORA TESTE",
14
+ "SUB_ESTIPULANTE_RAZAO_SOCIAL": "CORRETORA TESTE LTDA",
15
+ "PESSOA": "J",
16
+ "CNPJ_CPF_CLIENTE": "98765432000188",
17
+ "NOME_FANTASIA_CLIENTE": "CLIENTE TESTE",
18
+ "RAZAO_SOCIAL_CLIENTE": "CLIENTE TESTE LTDA",
19
+ "PLACA_VEICULO": "ABC1D23",
20
+ "CHASSI": "9BWZZZ377VT004251",
21
+ "CODE_FIPE": "005541-7",
22
+ "NME_BENEFICIO": "Casco PT (VEÍCULO)",
23
+ "VALOR_MENSALIDADE": "100.00",
24
+ "IMPORTANCIA_SEGURADA": "50000",
25
+ "EMPRESA_RASTREAMENTO": "TRACKERLOG",
26
+ "CATEGORIA": "3",
27
+ "MODELO": "FH 540",
28
+ "RENAVAM": "12345678901",
29
+ "MARCA": "VOLVO",
30
+ "COR": "BRANCO",
31
+ "ANO": "2022",
32
+ "EIXOS": "3",
33
+ "CAMBIO": "AUTOMATICO",
34
+ "ALIENADO": "SIM",
35
+ }
36
+ row.update(overrides)
37
+ return row
38
+
39
+
40
+ def _build_item(rows):
41
+ df = pd.DataFrame(rows)
42
+ canonical = ApoliceBuilder().build(df)[0]
43
+ return InternalMapper().map(canonical).data.items[0]
44
+
45
+
46
+ def test_vehicle_details_mapped_from_spreadsheet_columns():
47
+ item = _build_item([_base_row()])
48
+
49
+ assert item.item_type == "veiculo"
50
+ assert item.brand_name == "VOLVO"
51
+ assert item.model_description == "FH 540"
52
+ assert item.brand == "VOLVO"
53
+ assert item.model == "FH 540"
54
+ assert item.vehicle_classification == 3
55
+ assert item.renavam == "12345678901"
56
+ assert item.color == "BRANCO"
57
+ assert item.model_year == 2022
58
+ assert item.manufacture_year == 2022
59
+ assert item.axles == 3
60
+ assert item.transmission == "AUTOMATICO"
61
+ assert item.is_alienated is True
62
+ assert item.has_vehicle_tracker is False
63
+ assert item.vehicle_tracker_company is None
64
+
65
+
66
+ def test_vehicle_tracker_company_only_when_has_tracker():
67
+ with_tracker = _build_item([
68
+ _base_row(NME_BENEFICIO="Casco PT (VEÍCULO)", VALOR_MENSALIDADE="100.00"),
69
+ _base_row(
70
+ NME_BENEFICIO="Rastreador p/ Veículo",
71
+ VALOR_MENSALIDADE="15.00",
72
+ EMPRESA_RASTREAMENTO="TRACKERLOG",
73
+ ),
74
+ ])
75
+ without_tracker = _build_item([_base_row()])
76
+
77
+ assert with_tracker.has_vehicle_tracker is True
78
+ assert with_tracker.vehicle_tracker_company == "TRACKERLOG"
79
+
80
+ assert without_tracker.has_vehicle_tracker is False
81
+ assert without_tracker.vehicle_tracker_company is None
82
+
83
+
84
+ def test_alienado_na_is_false():
85
+ item = _build_item([_base_row(ALIENADO="N/A")])
86
+
87
+ assert item.is_alienated is False
88
+
89
+
90
+ def test_rastreamento_maps_to_tracker():
91
+ item = _build_item([
92
+ _base_row(NME_BENEFICIO="Casco PT (VEÍCULO)", VALOR_MENSALIDADE="100.00"),
93
+ _base_row(
94
+ NME_BENEFICIO="Rastreamento",
95
+ VALOR_MENSALIDADE="12.50",
96
+ EMPRESA_RASTREAMENTO="TRACKERLOG",
97
+ ),
98
+ ])
99
+
100
+ assert item.has_vehicle_tracker is True
101
+ assert item.vehicle_tracker_premium == 12.50
102
+ assert item.vehicle_tracker_company == "TRACKERLOG"
103
+ assert item.has_monitored_emergency_support is False
104
+
105
+
106
+ def test_rastreamento_reboque_maps_to_tracker():
107
+ rows = [
108
+ {
109
+ **_base_row(
110
+ PLACA_VEICULO="XYZ9A99",
111
+ CODE_FIPE=None,
112
+ NME_BENEFICIO="Casco PT (R/SR)",
113
+ VALOR_MENSALIDADE="50.00",
114
+ IMPORTANCIA_SEGURADA="50000",
115
+ ),
116
+ "MARCA_REBOQUE": "RANDON",
117
+ "MODELO_REBOQUE": "SR BAU",
118
+ },
119
+ {
120
+ **_base_row(
121
+ PLACA_VEICULO="XYZ9A99",
122
+ CODE_FIPE=None,
123
+ NME_BENEFICIO="Rastreamento Reboque/Semirreboque",
124
+ VALOR_MENSALIDADE="8.00",
125
+ ),
126
+ "MARCA_REBOQUE": "RANDON",
127
+ "MODELO_REBOQUE": "SR BAU",
128
+ },
129
+ ]
130
+ item = _build_item(rows)
131
+
132
+ assert item.item_type == "reboque"
133
+ assert item.has_vehicle_tracker is True
134
+ assert item.vehicle_tracker_premium == 8.00
135
+
136
+
137
+ def test_monitored_emergency_support_isolated_from_tracker():
138
+ item = _build_item([
139
+ _base_row(NME_BENEFICIO="Casco PT (VEÍCULO)", VALOR_MENSALIDADE="100.00"),
140
+ _base_row(
141
+ NME_BENEFICIO="SUPORTE EMERGENCIAL MONITORADO",
142
+ VALOR_MENSALIDADE="13.47",
143
+ EMPRESA_RASTREAMENTO="TRACKERLOG",
144
+ ),
145
+ ])
146
+
147
+ assert item.has_monitored_emergency_support is True
148
+ assert item.monitored_emergency_support_premium == 13.47
149
+ assert item.monitored_emergency_support_value == 1
150
+ assert item.has_vehicle_tracker is False
151
+ assert item.vehicle_tracker_company is None
152
+
153
+
154
+ def test_repair_assistance_consolidates_into_hull_premium():
155
+ item = _build_item([
156
+ _base_row(
157
+ NME_BENEFICIO="Casco PT (VEÍCULO)",
158
+ VALOR_MENSALIDADE="372.24",
159
+ IMPORTANCIA_SEGURADA="308130",
160
+ ),
161
+ _base_row(
162
+ NME_BENEFICIO="Assistência a Reparos (VEÍCULO)",
163
+ VALOR_MENSALIDADE="267.38",
164
+ IMPORTANCIA_SEGURADA="308130",
165
+ ),
166
+ _base_row(
167
+ NME_BENEFICIO="Casco PT (COMPLEMENTO)",
168
+ VALOR_MENSALIDADE="25.64",
169
+ IMPORTANCIA_SEGURADA="27000",
170
+ ),
171
+ _base_row(
172
+ NME_BENEFICIO="Assistência a Reparos (COMPLEMENTO)",
173
+ VALOR_MENSALIDADE="21.86",
174
+ IMPORTANCIA_SEGURADA="27000",
175
+ ),
176
+ ])
177
+
178
+ assert item.hull_premium == 687.12
179
+ assert item.hull_value == 308130
180
+ assert item.repair_assistance_premium == 0
181
+ assert item.has_repair_assistance is False